
+++ client/resources/img/arrow-rg.png
Binary file is not shown |
+++ client/resources/img/arrow.png
Binary file is not shown |
--- client/resources/scss/admin/content.scss
+++ client/resources/scss/admin/content.scss
... | ... | @@ -1,10 +1,12 @@ |
1 |
-.login{ |
|
1 |
+.login { |
|
2 | 2 |
background-color: #EFF1FA; |
3 | 3 |
height: 100vh; |
4 |
- .card{ |
|
4 |
+ |
|
5 |
+ .card { |
|
5 | 6 |
padding-top: 30rem; |
6 |
- .card-body{ |
|
7 |
- |
|
7 |
+ |
|
8 |
+ .card-body { |
|
9 |
+ |
|
8 | 10 |
background-color: #fff; |
9 | 11 |
margin: 0 auto; |
10 | 12 |
width: 600px; |
... | ... | @@ -15,69 +17,102 @@ |
15 | 17 |
gap: 30px; |
16 | 18 |
flex-direction: column; |
17 | 19 |
} |
18 |
- .card-title{ |
|
20 |
+ |
|
21 |
+ .card-title { |
|
19 | 22 |
text-align: center; |
20 | 23 |
} |
21 |
- .form-label{ |
|
22 |
- display: flex |
|
23 |
- ; |
|
24 |
- align-items: center; |
|
25 |
- font-weight: 700; |
|
26 |
- font-size: 15px; |
|
27 |
- margin-bottom: 10px; |
|
28 |
- img{margin-right: 3px;} |
|
24 |
+ |
|
25 |
+ .form-label { |
|
26 |
+ display: flex; |
|
27 |
+ align-items: center; |
|
28 |
+ font-weight: 700; |
|
29 |
+ font-size: 15px; |
|
30 |
+ margin-bottom: 10px; |
|
31 |
+ |
|
32 |
+ img { |
|
33 |
+ margin-right: 3px; |
|
34 |
+ } |
|
29 | 35 |
} |
30 |
- .box{margin-bottom: 30px; |
|
31 |
- button{width: 100%; background-color: #1F3F99; |
|
32 |
- img{margin-right: 10px;} |
|
36 |
+ |
|
37 |
+ .box { |
|
38 |
+ margin-bottom: 30px; |
|
39 |
+ |
|
40 |
+ button { |
|
41 |
+ width: 100%; |
|
42 |
+ background-color: #1F3F99; |
|
43 |
+ |
|
44 |
+ img { |
|
45 |
+ margin-right: 10px; |
|
46 |
+ } |
|
33 | 47 |
} |
34 | 48 |
} |
35 | 49 |
} |
36 |
- |
|
50 |
+ |
|
37 | 51 |
} |
38 |
-.sidemenu{ |
|
39 |
- width: 350px; |
|
52 |
+ |
|
53 |
+.sidemenu { |
|
54 |
+ width: 350px; |
|
40 | 55 |
margin-right: 20px; |
41 |
- .myinfo{ |
|
56 |
+ |
|
57 |
+ .myinfo { |
|
42 | 58 |
text-align: center; |
43 | 59 |
padding: 25px 20px 20px 20px; |
44 | 60 |
margin-bottom: 20px; |
45 | 61 |
border-radius: 20px; |
46 | 62 |
background: linear-gradient(to bottom, #1F3F99, #3354CE); |
47 |
- .name-box{ |
|
63 |
+ |
|
64 |
+ .name-box { |
|
48 | 65 |
margin-bottom: 30px; |
49 |
- .img-area{ |
|
66 |
+ |
|
67 |
+ .img-area { |
|
50 | 68 |
margin: 0 auto; |
51 | 69 |
width: 165px; |
52 |
- |
|
53 |
- .name{ |
|
70 |
+ |
|
71 |
+ .name { |
|
54 | 72 |
font-size: 25px; |
55 | 73 |
font-weight: 700; |
56 | 74 |
color: #fff; |
57 | 75 |
} |
58 |
- .info{ |
|
76 |
+ |
|
77 |
+ .info { |
|
59 | 78 |
background-color: #0A216D; |
60 | 79 |
border-radius: 100px; |
61 | 80 |
display: flex; |
62 | 81 |
align-items: center; |
63 | 82 |
gap: 10px; |
64 | 83 |
justify-content: center; |
65 |
- |
|
66 |
- p{color: #fff; font-size: 15px;} |
|
84 |
+ |
|
85 |
+ p { |
|
86 |
+ color: #fff; |
|
87 |
+ font-size: 15px; |
|
88 |
+ } |
|
67 | 89 |
} |
68 | 90 |
} |
69 | 91 |
} |
70 |
- |
|
71 |
- .info-box{ |
|
92 |
+ |
|
93 |
+ .info-box { |
|
72 | 94 |
background-color: #fff; |
73 | 95 |
border-radius: 15px; |
74 | 96 |
display: flex; |
75 | 97 |
justify-content: space-between; |
76 |
- li{text-align: center; border-right: 1px solid #E7EAF4; width: 25%; |
|
77 |
- p{font-size: 35px; font-weight: 700; margin: 20px 0;} |
|
98 |
+ |
|
99 |
+ li { |
|
100 |
+ text-align: center; |
|
101 |
+ border-right: 1px solid #E7EAF4; |
|
102 |
+ width: 25%; |
|
103 |
+ |
|
104 |
+ p { |
|
105 |
+ font-size: 35px; |
|
106 |
+ font-weight: 700; |
|
107 |
+ margin: 20px 0; |
|
108 |
+ } |
|
78 | 109 |
} |
79 |
- li:last-child{border: 0;} |
|
80 |
- .name{ |
|
110 |
+ |
|
111 |
+ li:last-child { |
|
112 |
+ border: 0; |
|
113 |
+ } |
|
114 |
+ |
|
115 |
+ .name { |
|
81 | 116 |
text-align: center; |
82 | 117 |
display: flex; |
83 | 118 |
justify-content: center; |
... | ... | @@ -91,25 +126,52 @@ |
91 | 126 |
color: #fff; |
92 | 127 |
border-radius: 100px; |
93 | 128 |
} |
94 |
- .name.yellow{background-color:#EDAB0C;} |
|
95 |
- .name.blue{background-color:#1D75E1;} |
|
96 |
- .name.red{background-color:#E92727;} |
|
129 |
+ |
|
130 |
+ .name.yellow { |
|
131 |
+ background-color: #EDAB0C; |
|
132 |
+ } |
|
133 |
+ |
|
134 |
+ .name.blue { |
|
135 |
+ background-color: #1D75E1; |
|
136 |
+ } |
|
137 |
+ |
|
138 |
+ .name.red { |
|
139 |
+ background-color: #E92727; |
|
140 |
+ } |
|
97 | 141 |
} |
98 | 142 |
} |
99 |
- .myinfo.profile{ |
|
143 |
+ |
|
144 |
+ .myinfo.profile { |
|
100 | 145 |
background: #fff; |
101 |
- .name-box{margin: 20px 0;} |
|
102 |
- .img-area{ |
|
146 |
+ |
|
147 |
+ .name-box { |
|
148 |
+ margin: 20px 0; |
|
149 |
+ } |
|
150 |
+ |
|
151 |
+ .img-area { |
|
103 | 152 |
width: 100%; |
104 |
- .img{ |
|
153 |
+ |
|
154 |
+ .img { |
|
105 | 155 |
position: relative; |
106 |
- width: 150px; height: 190px; border: 1px solid #D7DCF1; |
|
156 |
+ width: 150px; |
|
157 |
+ height: 190px; |
|
158 |
+ border: 1px solid #D7DCF1; |
|
107 | 159 |
padding: 10px; |
108 | 160 |
margin: 0 auto; |
109 |
- .close-btn{position: absolute; top: 0; right: 10px;} |
|
110 |
- img{height: 100%; object-fit: cover; } |
|
161 |
+ |
|
162 |
+ .close-btn { |
|
163 |
+ position: absolute; |
|
164 |
+ top: 0; |
|
165 |
+ right: 10px; |
|
166 |
+ } |
|
167 |
+ |
|
168 |
+ img { |
|
169 |
+ height: 100%; |
|
170 |
+ object-fit: cover; |
|
171 |
+ } |
|
111 | 172 |
} |
112 |
- .info{ |
|
173 |
+ |
|
174 |
+ .info { |
|
113 | 175 |
border-top: 1px solid #9498A94D; |
114 | 176 |
margin: 0 auto; |
115 | 177 |
padding-top: 30px; |
... | ... | @@ -117,122 +179,227 @@ |
117 | 179 |
width: 240px; |
118 | 180 |
background-color: transparent; |
119 | 181 |
border-radius: 0; |
182 |
+ |
|
120 | 183 |
.file { |
121 |
- background-color: #EFF1FA; width: 120px; border: #213F9A 1px solid ; border-radius: 10px; padding: 7px 0; |
|
184 |
+ background-color: #EFF1FA; |
|
185 |
+ width: 120px; |
|
186 |
+ border: #213F9A 1px solid; |
|
187 |
+ border-radius: 10px; |
|
188 |
+ padding: 7px 0; |
|
122 | 189 |
margin-top: 10px; |
123 |
- } |
|
124 |
- |
|
125 |
- .file-label { |
|
190 |
+ } |
|
191 |
+ |
|
192 |
+ .file-label { |
|
126 | 193 |
display: flex; |
127 | 194 |
gap: 5px; |
128 | 195 |
justify-content: center; |
129 | 196 |
cursor: pointer; |
130 |
- p{color: #000;} |
|
131 |
- } |
|
132 |
- |
|
133 |
- input[type="file"] { |
|
197 |
+ |
|
198 |
+ p { |
|
199 |
+ color: #000; |
|
200 |
+ } |
|
201 |
+ } |
|
202 |
+ |
|
203 |
+ input[type="file"] { |
|
134 | 204 |
display: none; |
135 |
- } |
|
136 |
- } |
|
205 |
+ } |
|
206 |
+ } |
|
137 | 207 |
} |
138 | 208 |
} |
139 |
- .myinfo.simple{ |
|
209 |
+ |
|
210 |
+ .myinfo.simple { |
|
140 | 211 |
background: transparent; |
141 |
- .img-area{ |
|
142 |
- img{width: 75px; |
|
143 |
- height: 75px;} |
|
144 |
- .info{ |
|
212 |
+ |
|
213 |
+ .img-area { |
|
214 |
+ img { |
|
215 |
+ width: 75px; |
|
216 |
+ height: 75px; |
|
217 |
+ } |
|
218 |
+ |
|
219 |
+ .info { |
|
145 | 220 |
background-color: #DEE2F5; |
146 |
- p{color: #000;} |
|
147 |
- .fa-bars{ |
|
221 |
+ |
|
222 |
+ p { |
|
223 |
+ color: #000; |
|
224 |
+ } |
|
225 |
+ |
|
226 |
+ .fa-bars { |
|
148 | 227 |
width: 2px; |
149 | 228 |
height: 14px; |
150 | 229 |
background-color: #213F9A1A; |
151 | 230 |
} |
152 |
- } |
|
153 |
- .name{color: #000;} |
|
231 |
+ } |
|
232 |
+ |
|
233 |
+ .name { |
|
234 |
+ color: #000; |
|
235 |
+ } |
|
154 | 236 |
} |
155 |
- details[open] > summary { |
|
156 |
- |
|
237 |
+ |
|
238 |
+ details[open]>summary { |
|
239 |
+ |
|
157 | 240 |
background: #213F9A; |
158 | 241 |
color: #fff; |
159 |
- |
|
160 |
- .icon{display: block;} |
|
161 |
- } |
|
242 |
+ |
|
243 |
+ .icon { |
|
244 |
+ display: block; |
|
245 |
+ } |
|
246 |
+ } |
|
162 | 247 |
|
163 | 248 |
} |
164 |
- .menu-box{ |
|
165 |
- summary{ |
|
249 |
+ |
|
250 |
+ .menu-box { |
|
251 |
+ summary { |
|
166 | 252 |
cursor: pointer; |
167 | 253 |
border: 1px solid #213F9A; |
168 | 254 |
color: #000; |
169 | 255 |
display: flex; |
170 | 256 |
justify-content: space-between; |
171 | 257 |
font-size: 17px; |
172 |
- text-align: left; |
|
173 |
- padding: 10px 15px; |
|
258 |
+ text-align: left; |
|
259 |
+ padding: 10px 15px; |
|
174 | 260 |
border-radius: 10px; |
175 |
- .icon{display: none;} |
|
261 |
+ |
|
262 |
+ .icon { |
|
263 |
+ display: none; |
|
264 |
+ } |
|
176 | 265 |
} |
177 |
- |
|
266 |
+ |
|
178 | 267 |
margin-top: 20px; |
179 |
- ul{padding: 10px 10px 0px 10px;} |
|
180 |
- li{ |
|
268 |
+ |
|
269 |
+ ul { |
|
270 |
+ padding: 10px 10px 0px 10px; |
|
271 |
+ } |
|
272 |
+ |
|
273 |
+ li { |
|
181 | 274 |
margin-bottom: 10px; |
182 |
- a{display: flex; justify-content: space-between;} |
|
183 |
- |
|
184 |
- |
|
275 |
+ |
|
276 |
+ a { |
|
277 |
+ display: flex; |
|
278 |
+ justify-content: space-between; |
|
279 |
+ width: 100%; |
|
280 |
+ } |
|
281 |
+ |
|
282 |
+ |
|
185 | 283 |
} |
186 | 284 |
} |
187 |
- .boxs{ |
|
188 |
- background-color: #fff; border-radius: 20px; padding: 20px; margin-bottom: 20px; |
|
189 |
- .box{display: flex; justify-content: space-between; margin-bottom: 15px; |
|
190 |
- h3{text-align: center; |
|
285 |
+ .menu-box.danil{ |
|
286 |
+ a{display: block; |
|
287 |
+ cursor: pointer; |
|
288 |
+ border: 1px solid #213F9A; |
|
289 |
+ color: #000; |
|
290 |
+ |
|
291 |
+ font-size: 17px; |
|
292 |
+ text-align: left; |
|
293 |
+ padding: 10px 15px; |
|
294 |
+ border-radius: 10px; |
|
295 |
+ margin-bottom: 10px; |
|
296 |
+ li{ |
|
297 |
+ display: flex; |
|
298 |
+ justify-content: space-between; |
|
299 |
+ margin-bottom: 0px; |
|
300 |
+ .icon { |
|
301 |
+ display: none; |
|
302 |
+ } |
|
303 |
+ } |
|
304 |
+ } |
|
305 |
+ a.active-link{ |
|
306 |
+ background: #213F9A; |
|
307 |
+ li{ |
|
308 |
+ p{ |
|
309 |
+ color: #fff; |
|
310 |
+ } |
|
311 |
+ .icon { |
|
312 |
+ display: block; |
|
313 |
+ } |
|
314 |
+ } |
|
315 |
+ } |
|
316 |
+ } |
|
317 |
+ .boxs { |
|
318 |
+ background-color: #fff; |
|
319 |
+ border-radius: 20px; |
|
320 |
+ padding: 20px; |
|
321 |
+ margin-bottom: 20px; |
|
322 |
+ |
|
323 |
+ .box { |
|
324 |
+ display: flex; |
|
325 |
+ justify-content: space-between; |
|
326 |
+ margin-bottom: 15px; |
|
327 |
+ |
|
328 |
+ h3 { |
|
329 |
+ text-align: center; |
|
191 | 330 |
color: #fff; |
192 | 331 |
font-size: 16px; |
193 |
- font-weight: 700;} |
|
194 |
- } |
|
195 |
- .img-area{margin: 0 12.5px;} |
|
196 |
- h4{ |
|
197 |
- font-size: 19px; |
|
198 | 332 |
font-weight: 700; |
199 |
- text-align: center; |
|
200 |
- line-height: 22px; |
|
201 |
- img{margin-right: 10px;} |
|
202 |
- margin-bottom: 15px; |
|
333 |
+ } |
|
203 | 334 |
} |
204 |
- i.fa-bars{display: block; width: 2px; height: 14px; background-color: #D7DCF1;} |
|
205 |
- .sm-box{ |
|
335 |
+ |
|
336 |
+ .img-area { |
|
337 |
+ margin: 0 12.5px; |
|
338 |
+ } |
|
339 |
+ |
|
340 |
+ h4 { |
|
341 |
+ font-size: 19px; |
|
342 |
+ font-weight: 700; |
|
343 |
+ text-align: center; |
|
344 |
+ line-height: 22px; |
|
345 |
+ |
|
346 |
+ img { |
|
347 |
+ margin-right: 10px; |
|
348 |
+ } |
|
349 |
+ |
|
350 |
+ margin-bottom: 15px; |
|
351 |
+ } |
|
352 |
+ |
|
353 |
+ i.fa-bars { |
|
354 |
+ display: block; |
|
355 |
+ width: 2px; |
|
356 |
+ height: 14px; |
|
357 |
+ background-color: #D7DCF1; |
|
358 |
+ } |
|
359 |
+ |
|
360 |
+ .sm-box { |
|
206 | 361 |
width: 185px; |
207 | 362 |
background-color: #F4F6FD; |
208 | 363 |
border-radius: 10px; |
209 | 364 |
text-align: center; |
210 |
- h3{ |
|
365 |
+ |
|
366 |
+ h3 { |
|
211 | 367 |
background-color: #213F9A; |
212 | 368 |
border-radius: 10px; |
213 | 369 |
height: 35px; |
214 | 370 |
line-height: 35px; |
215 | 371 |
} |
216 |
- p{ |
|
372 |
+ |
|
373 |
+ p { |
|
217 | 374 |
font-size: 30px; |
218 | 375 |
font-weight: 700; |
219 | 376 |
} |
220 | 377 |
} |
221 |
- .bd-box{ |
|
378 |
+ |
|
379 |
+ .bd-box { |
|
222 | 380 |
border: 1px solid #D7DCF1; |
223 | 381 |
border-radius: 10px; |
224 | 382 |
padding: 19px 40px 19px 40px; |
225 | 383 |
display: flex; |
226 | 384 |
align-items: center; |
227 | 385 |
gap: 30px; |
228 |
- div{font-size: 18px; |
|
229 |
- span{font-size: 18px; font-weight: 800; margin-left:23px;} |
|
386 |
+ |
|
387 |
+ div { |
|
388 |
+ font-size: 18px; |
|
389 |
+ |
|
390 |
+ span { |
|
391 |
+ font-size: 18px; |
|
392 |
+ font-weight: 800; |
|
393 |
+ margin-left: 23px; |
|
394 |
+ } |
|
230 | 395 |
} |
231 | 396 |
} |
232 |
- .color-boxs{ |
|
397 |
+ |
|
398 |
+ .color-boxs { |
|
233 | 399 |
display: flex; |
234 | 400 |
justify-content: space-between; |
235 |
- .box{ |
|
401 |
+ |
|
402 |
+ .box { |
|
236 | 403 |
display: block; |
237 | 404 |
text-align: center; |
238 | 405 |
width: 96px; |
... | ... | @@ -241,35 +408,54 @@ |
241 | 408 |
font-weight: 700; |
242 | 409 |
border-radius: 10px; |
243 | 410 |
padding-bottom: 24px; |
244 |
- h3{background-color: #999999; border-top-left-radius: 10px; border-top-right-radius: 10px; width: 100%; height: 33px; |
|
411 |
+ |
|
412 |
+ h3 { |
|
413 |
+ background-color: #999999; |
|
414 |
+ border-top-left-radius: 10px; |
|
415 |
+ border-top-right-radius: 10px; |
|
416 |
+ width: 100%; |
|
417 |
+ height: 33px; |
|
245 | 418 |
line-height: 33px; |
246 |
- margin-bottom: 17px;} |
|
419 |
+ margin-bottom: 17px; |
|
420 |
+ } |
|
247 | 421 |
} |
248 |
- .box.red{ |
|
422 |
+ |
|
423 |
+ .box.red { |
|
249 | 424 |
background-color: #FEF3F3; |
250 |
- h3{background-color: #E92727;} |
|
425 |
+ |
|
426 |
+ h3 { |
|
427 |
+ background-color: #E92727; |
|
428 |
+ } |
|
251 | 429 |
|
252 | 430 |
} |
253 |
- .box.green{ |
|
431 |
+ |
|
432 |
+ .box.green { |
|
254 | 433 |
background-color: #EFF9FB; |
255 |
- h3{background-color: #3C97AB;} |
|
434 |
+ |
|
435 |
+ h3 { |
|
436 |
+ background-color: #3C97AB; |
|
437 |
+ } |
|
256 | 438 |
|
257 | 439 |
} |
258 | 440 |
|
259 | 441 |
} |
260 |
- |
|
442 |
+ |
|
261 | 443 |
} |
444 |
+ |
|
262 | 445 |
.router-link-active p { |
263 |
- color: #213F9A; /* 원하는 색상으로 변경 */ |
|
446 |
+ color: #213F9A; |
|
447 |
+ /* 원하는 색상으로 변경 */ |
|
264 | 448 |
font-weight: 700; |
265 |
- } |
|
266 |
- |
|
267 |
- |
|
449 |
+ } |
|
450 |
+ |
|
451 |
+ |
|
268 | 452 |
} |
269 |
-.content{ |
|
270 |
- .pagination{ |
|
453 |
+ |
|
454 |
+.content { |
|
455 |
+ .pagination { |
|
271 | 456 |
margin-top: 20px; |
272 | 457 |
margin-bottom: 5rem; |
458 |
+ |
|
273 | 459 |
ul { |
274 | 460 |
display: flex; |
275 | 461 |
list-style: none; |
... | ... | @@ -277,121 +463,181 @@ |
277 | 463 |
gap: 8px; |
278 | 464 |
justify-content: center; |
279 | 465 |
align-items: center; |
280 |
- } |
|
281 |
- |
|
282 |
- li { |
|
466 |
+ } |
|
467 |
+ |
|
468 |
+ li { |
|
283 | 469 |
width: 35px; |
284 | 470 |
height: 35px; |
285 | 471 |
text-align: center; |
286 |
- line-height: 35px; |
|
472 |
+ line-height: 35px; |
|
287 | 473 |
border: 1px solid #BDCBE8; |
288 | 474 |
border-radius: 4px; |
289 | 475 |
cursor: pointer; |
290 | 476 |
background-color: #fff; |
291 | 477 |
color: #333; |
292 | 478 |
transition: 0.2s; |
293 |
- } |
|
294 |
- |
|
295 |
- li:hover:not(.disabled) { |
|
479 |
+ } |
|
480 |
+ |
|
481 |
+ li:hover:not(.disabled) { |
|
296 | 482 |
background-color: #f0f0f0; |
297 |
- } |
|
298 |
- |
|
299 |
- li.active { |
|
483 |
+ } |
|
484 |
+ |
|
485 |
+ li.active { |
|
300 | 486 |
background-color: #1D75E1; |
301 | 487 |
color: white; |
302 | 488 |
font-weight: bold; |
303 |
- } |
|
304 |
- |
|
305 |
- li.arrow { |
|
489 |
+ } |
|
490 |
+ |
|
491 |
+ li.arrow { |
|
306 | 492 |
font-weight: bold; |
307 |
- } |
|
308 |
- |
|
309 |
- li.disabled { |
|
493 |
+ } |
|
494 |
+ |
|
495 |
+ li.disabled { |
|
310 | 496 |
opacity: 0.5; |
311 | 497 |
cursor: not-allowed; |
312 |
- } |
|
498 |
+ } |
|
313 | 499 |
} |
314 |
- |
|
315 |
- .title{ |
|
500 |
+ |
|
501 |
+ .title { |
|
316 | 502 |
display: flex; |
317 | 503 |
justify-content: space-between; |
318 | 504 |
align-items: center; |
319 | 505 |
margin-bottom: 10px; |
320 |
- |
|
321 |
- h2{font-size: 24px; font-weight: 700; display: flex; gap: 20px; |
|
322 |
- span{border: 1px solid #E92727; border-radius: 100px; color: #E92727; font-size: 15px; text-align: center; padding: 5px 20px;} |
|
506 |
+ |
|
507 |
+ h2 { |
|
508 |
+ font-size: 24px; |
|
509 |
+ font-weight: 700; |
|
510 |
+ display: flex; |
|
511 |
+ gap: 20px; |
|
512 |
+ |
|
513 |
+ span { |
|
514 |
+ border: 1px solid #E92727; |
|
515 |
+ border-radius: 100px; |
|
516 |
+ color: #E92727; |
|
517 |
+ font-size: 15px; |
|
518 |
+ text-align: center; |
|
519 |
+ padding: 5px 20px; |
|
520 |
+ } |
|
323 | 521 |
} |
324 |
- .sub{display: flex; align-items: center; gap: 3px;} |
|
522 |
+ |
|
523 |
+ .sub { |
|
524 |
+ display: flex; |
|
525 |
+ align-items: center; |
|
526 |
+ gap: 3px; |
|
527 |
+ } |
|
325 | 528 |
} |
326 |
- .top-content{display: flex; justify-content: space-between; gap: 40px; margin-bottom: 15px;} |
|
327 |
- .boxs{ |
|
529 |
+ |
|
530 |
+ .top-content { |
|
531 |
+ display: flex; |
|
532 |
+ justify-content: space-between; |
|
533 |
+ gap: 40px; |
|
534 |
+ margin-bottom: 15px; |
|
535 |
+ } |
|
536 |
+ |
|
537 |
+ .boxs { |
|
328 | 538 |
// width: 550px; |
329 |
- |
|
330 |
- .blue-box{ |
|
539 |
+ |
|
540 |
+ .blue-box { |
|
331 | 541 |
background-color: #D7DCF1; |
332 |
- border-radius: 10px; |
|
542 |
+ border-radius: 10px; |
|
333 | 543 |
padding: 30px; |
334 |
- |
|
335 |
- .box{background-color: #fff; text-align: center; |
|
544 |
+ |
|
545 |
+ .box { |
|
546 |
+ background-color: #fff; |
|
547 |
+ text-align: center; |
|
336 | 548 |
padding: 29px 0; |
337 | 549 |
border-radius: 10px; |
338 |
- .time{font-size: 35px; font-weight: 700;} |
|
339 |
- |
|
550 |
+ |
|
551 |
+ .time { |
|
552 |
+ font-size: 35px; |
|
553 |
+ font-weight: 700; |
|
554 |
+ } |
|
555 |
+ |
|
340 | 556 |
} |
341 |
- .buttons{ |
|
342 |
- display: flex; gap: 25px; |
|
557 |
+ |
|
558 |
+ .buttons { |
|
559 |
+ display: flex; |
|
560 |
+ gap: 25px; |
|
343 | 561 |
align-items: center; |
344 | 562 |
justify-content: center; |
345 | 563 |
margin-top: 10px; |
346 |
- i.fa-bars{display: block; width: 1px; height: 40px; background-color: #fff;} |
|
564 |
+ |
|
565 |
+ i.fa-bars { |
|
566 |
+ display: block; |
|
567 |
+ width: 1px; |
|
568 |
+ height: 40px; |
|
569 |
+ background-color: #fff; |
|
570 |
+ } |
|
347 | 571 |
} |
348 | 572 |
} |
349 |
- .board{ |
|
350 |
- border: 1px solid #DDDDDD; border-radius: 10px; |
|
351 |
- table{ |
|
352 | 573 |
|
353 |
- tr{ border-bottom: 1px solid #DDDDDD;} |
|
354 |
- tr:last-child{border: 0;} |
|
355 |
- td{ |
|
574 |
+ .board { |
|
575 |
+ border: 1px solid #DDDDDD; |
|
576 |
+ border-radius: 10px; |
|
577 |
+ |
|
578 |
+ table { |
|
579 |
+ |
|
580 |
+ tr { |
|
581 |
+ border-bottom: 1px solid #DDDDDD; |
|
582 |
+ } |
|
583 |
+ |
|
584 |
+ tr:last-child { |
|
585 |
+ border: 0; |
|
586 |
+ } |
|
587 |
+ |
|
588 |
+ td { |
|
356 | 589 |
border-radius: 10px; |
357 | 590 |
border: 0; |
358 | 591 |
text-align: center; |
359 |
- p{ |
|
592 |
+ |
|
593 |
+ p { |
|
360 | 594 |
padding: 4px 0; |
361 | 595 |
border-radius: 5px; |
362 | 596 |
width: 85px; |
363 | 597 |
} |
364 | 598 |
} |
365 |
- .category-service p{ |
|
366 |
- background-color: #FED9AC; |
|
367 |
- } |
|
368 |
- |
|
369 |
- .category-internal p{ |
|
370 |
- background-color: #EFF1FA; |
|
371 |
- } |
|
372 |
- |
|
373 |
- .category-government p{ |
|
374 |
- background-color: #DFD4F0; |
|
375 |
- } |
|
376 |
- .status p{ |
|
377 |
- border: 2px solid #AAAAAA; |
|
378 |
- border-radius: 100px; |
|
379 |
- color: #AAAAAA; |
|
380 |
- font-size: 15px; |
|
381 |
- } |
|
382 |
- .status-in-progress p{ border-color: #1D75E1; color: #1D75E1;} |
|
383 |
- .status-complete p{ color: rgb(39, 96, 55); border-color: rgb(39, 96, 55); } |
|
384 |
- |
|
599 |
+ |
|
600 |
+ .category-service p { |
|
601 |
+ background-color: #FED9AC; |
|
602 |
+ } |
|
603 |
+ |
|
604 |
+ .category-internal p { |
|
605 |
+ background-color: #EFF1FA; |
|
606 |
+ } |
|
607 |
+ |
|
608 |
+ .category-government p { |
|
609 |
+ background-color: #DFD4F0; |
|
610 |
+ } |
|
611 |
+ |
|
612 |
+ .status p { |
|
613 |
+ border: 2px solid #AAAAAA; |
|
614 |
+ border-radius: 100px; |
|
615 |
+ color: #AAAAAA; |
|
616 |
+ font-size: 15px; |
|
617 |
+ } |
|
618 |
+ |
|
619 |
+ .status-in-progress p { |
|
620 |
+ border-color: #1D75E1; |
|
621 |
+ color: #1D75E1; |
|
622 |
+ } |
|
623 |
+ |
|
624 |
+ .status-complete p { |
|
625 |
+ color: rgb(39, 96, 55); |
|
626 |
+ border-color: rgb(39, 96, 55); |
|
627 |
+ } |
|
628 |
+ |
|
385 | 629 |
} |
386 | 630 |
} |
387 | 631 |
} |
388 |
- .card{ |
|
389 |
- .color-boxs{ |
|
632 |
+ |
|
633 |
+ .card { |
|
634 |
+ .color-boxs { |
|
390 | 635 |
display: flex; |
391 | 636 |
justify-content: space-between; |
392 | 637 |
gap: 10px; |
393 | 638 |
margin-bottom: 20px; |
394 |
- .box{ |
|
639 |
+ |
|
640 |
+ .box { |
|
395 | 641 |
display: block; |
396 | 642 |
text-align: center; |
397 | 643 |
width: calc(100% / 6); |
... | ... | @@ -400,306 +646,835 @@ |
400 | 646 |
font-weight: 700; |
401 | 647 |
border-radius: 10px; |
402 | 648 |
padding-bottom: 24px; |
403 |
- h3{background-color: #333333; color: #fff; border-radius: 10px;width: 100%; height: 50px; |
|
649 |
+ |
|
650 |
+ h3 { |
|
651 |
+ background-color: #333333; |
|
652 |
+ color: #fff; |
|
653 |
+ border-radius: 10px; |
|
654 |
+ width: 100%; |
|
655 |
+ height: 50px; |
|
404 | 656 |
line-height: 50px; |
405 |
- margin-bottom: 17px; font-size: 20px;} |
|
657 |
+ margin-bottom: 17px; |
|
658 |
+ font-size: 20px; |
|
659 |
+ } |
|
406 | 660 |
} |
407 |
- .box.red{ |
|
661 |
+ |
|
662 |
+ .box.red { |
|
408 | 663 |
background-color: #FEF3F3; |
409 |
- h3{background-color: #E92727;} |
|
664 |
+ |
|
665 |
+ h3 { |
|
666 |
+ background-color: #E92727; |
|
667 |
+ } |
|
410 | 668 |
|
411 | 669 |
} |
412 |
- .box.green{ |
|
670 |
+ |
|
671 |
+ .box.green { |
|
413 | 672 |
background-color: #EFF9FB; |
414 |
- h3{background-color: #3C97AB;} |
|
673 |
+ |
|
674 |
+ h3 { |
|
675 |
+ background-color: #3C97AB; |
|
676 |
+ } |
|
415 | 677 |
} |
416 |
- .box.blue{ |
|
678 |
+ |
|
679 |
+ .box.blue { |
|
417 | 680 |
background-color: #E9EFF8; |
418 |
- h3{background-color: #1D75E1;} |
|
681 |
+ |
|
682 |
+ h3 { |
|
683 |
+ background-color: #1D75E1; |
|
684 |
+ } |
|
419 | 685 |
} |
420 |
- .box.purple{ |
|
686 |
+ |
|
687 |
+ .box.purple { |
|
421 | 688 |
background-color: #F5EFFA; |
422 |
- h3{background-color: #A36CD4;} |
|
689 |
+ |
|
690 |
+ h3 { |
|
691 |
+ background-color: #A36CD4; |
|
692 |
+ } |
|
423 | 693 |
} |
424 |
- .box.orange{ |
|
694 |
+ |
|
695 |
+ .box.orange { |
|
425 | 696 |
background-color: #FFF8F3; |
426 |
- h3{background-color: #F7941C;} |
|
697 |
+ |
|
698 |
+ h3 { |
|
699 |
+ background-color: #F7941C; |
|
700 |
+ } |
|
427 | 701 |
} |
428 | 702 |
|
429 | 703 |
} |
430 |
- .name-box{ |
|
704 |
+ |
|
705 |
+ .name-box { |
|
431 | 706 |
margin-bottom: 20px; |
432 |
- .img-area{ |
|
707 |
+ |
|
708 |
+ .img-area { |
|
433 | 709 |
flex-shrink: 0; |
434 | 710 |
margin-right: 30px; |
435 | 711 |
width: 300px; |
436 |
- height: 150px; |
|
712 |
+ height: 180px; |
|
437 | 713 |
background-color: #EFF1FA; |
438 | 714 |
border-radius: 20px; |
439 |
- text-align: center; |
|
440 |
- padding: 20px; |
|
441 |
- .img{ |
|
715 |
+ align-items: center; |
|
716 |
+ justify-content: center; |
|
717 |
+ display: flex; |
|
718 |
+ |
|
719 |
+ |
|
720 |
+ .img { |
|
721 |
+ position: relative; |
|
442 | 722 |
display: flex; |
443 | 723 |
justify-self: center; |
444 | 724 |
background-color: #fff; |
445 | 725 |
width: 92px; |
446 | 726 |
height: 110px; |
447 | 727 |
padding: 8px; |
448 |
- img{object-fit: cover; width: 100%; |
|
449 |
- height: 100%;} |
|
728 |
+ .close-btn { |
|
729 |
+ position: absolute; |
|
730 |
+ top: 0; |
|
731 |
+ right: 10px; |
|
732 |
+ } |
|
733 |
+ img { |
|
734 |
+ |
|
735 |
+ object-fit: cover; |
|
736 |
+ width: 100%; |
|
737 |
+ height: 100%; |
|
738 |
+ } |
|
450 | 739 |
} |
451 | 740 |
} |
452 |
- form{ |
|
741 |
+ .img-area.column{ |
|
742 |
+ flex-direction: column; |
|
743 |
+ } |
|
744 |
+ |
|
745 |
+ form { |
|
453 | 746 |
input[readonly] { |
454 | 747 |
border-color: #fff; |
455 | 748 |
background-color: #fff !important; |
456 | 749 |
cursor: context-menu; |
457 |
- } |
|
458 |
- .col-12 { |
|
459 |
- input{margin: 4px 10px;} |
|
460 |
- label{line-height: 48px;} |
|
461 |
- } |
|
750 |
+ } |
|
751 |
+ |
|
752 |
+ .col-12 { |
|
753 |
+ input { |
|
754 |
+ margin: 9px 10px; |
|
755 |
+ } |
|
756 |
+ |
|
757 |
+ label { |
|
758 |
+ line-height: 48px; |
|
759 |
+ } |
|
760 |
+ } |
|
462 | 761 |
} |
463 | 762 |
|
464 | 763 |
} |
764 |
+ |
|
465 | 765 |
.sch-form-wrap { |
466 | 766 |
justify-self: end; |
467 | 767 |
margin-bottom: 20px; |
468 |
- .input-group{ |
|
469 |
- .form-select, .form-control{ |
|
768 |
+ |
|
769 |
+ .input-group { |
|
770 |
+ |
|
771 |
+ .form-select, |
|
772 |
+ .form-control { |
|
470 | 773 |
height: 40px; |
471 | 774 |
border-color: #C7CFE3; |
472 | 775 |
font-size: 16px; |
473 | 776 |
} |
474 |
- .form-control{padding-right: 30px; padding-left: 10px;} |
|
475 |
- .ico-sch{right: 5px;} |
|
476 |
- } |
|
477 |
- |
|
478 |
- } |
|
479 |
- .sch-form-wrap.title-wrap{justify-content: space-between; width: 100%; |
|
480 |
- h3{ |
|
481 |
- img{margin: 5px 5px 0 0;} |
|
482 |
- } |
|
483 |
- .input-group{width: auto; |
|
484 |
- |
|
485 |
- } |
|
486 |
- } |
|
487 |
- .tbl-wrap{ |
|
488 |
- table.buseo{ |
|
489 |
- tbody{ |
|
490 |
- tr{cursor: pointer;} |
|
777 |
+ |
|
778 |
+ .form-control { |
|
779 |
+ padding-right: 30px; |
|
780 |
+ padding-left: 10px; |
|
781 |
+ } |
|
782 |
+ |
|
783 |
+ .ico-sch { |
|
784 |
+ right: 5px; |
|
491 | 785 |
} |
492 | 786 |
} |
493 |
- th,td{text-align: center !important; font-size: 16px !important;} |
|
787 |
+ |
|
788 |
+ } |
|
789 |
+ |
|
790 |
+ .sch-form-wrap.title-wrap { |
|
791 |
+ justify-content: space-between; |
|
792 |
+ width: 100%; |
|
793 |
+ |
|
794 |
+ h3 { |
|
795 |
+ img { |
|
796 |
+ margin: 5px 5px 0 0; |
|
797 |
+ } |
|
798 |
+ } |
|
799 |
+ |
|
800 |
+ .input-group { |
|
801 |
+ width: auto; |
|
802 |
+ |
|
803 |
+ } |
|
804 |
+ } |
|
805 |
+ |
|
806 |
+ .tbl-wrap { |
|
807 |
+ table.buseo { |
|
808 |
+ tbody { |
|
809 |
+ tr { |
|
810 |
+ cursor: pointer; |
|
811 |
+ } |
|
812 |
+ } |
|
813 |
+ } |
|
814 |
+ |
|
815 |
+ th, |
|
816 |
+ td { |
|
817 |
+ text-align: center !important; |
|
818 |
+ font-size: 16px !important; |
|
819 |
+ } |
|
820 |
+ |
|
494 | 821 |
.status-approved { |
495 | 822 |
color: #1D75E1; |
496 | 823 |
font-weight: 700; |
497 |
- } |
|
498 |
- |
|
499 |
- .status-pending { |
|
824 |
+ } |
|
825 |
+ |
|
826 |
+ .status-pending { |
|
500 | 827 |
color: #E92727; |
501 | 828 |
font-weight: 700; |
502 |
- } |
|
503 |
- .expired td{ |
|
504 |
- background-color: #F6F6F6; /* 연회색 배경 */ |
|
829 |
+ } |
|
830 |
+ .status-green { |
|
831 |
+ color: #219A8C; |
|
832 |
+ font-weight: 700; |
|
833 |
+ } |
|
834 |
+ |
|
835 |
+ .expired td { |
|
836 |
+ background-color: #F6F6F6; |
|
837 |
+ /* 연회색 배경 */ |
|
505 | 838 |
filter: grayscale(50%); |
506 | 839 |
opacity: 0.6; |
507 |
- } |
|
508 |
- } |
|
509 |
- p.require{ |
|
510 |
- text-align: right; |
|
511 |
- img{margin-top: 9px;} |
|
512 | 840 |
} |
513 |
- .card-body{ |
|
514 |
- .flex{display: flex; align-items: center;} |
|
515 |
- .flex.sb{justify-content: space-between;} |
|
516 |
- .card-title{ |
|
517 |
- margin-bottom: 20px; |
|
518 |
- } |
|
519 |
- .sub{ |
|
520 |
- margin-right: 20px; |
|
521 |
- img{width: 50px; height: 50px;} |
|
522 |
- .date{margin-left: 10px;} |
|
523 |
- } |
|
524 |
- .sch-wrap{ |
|
525 |
- border: 1px solid #213F9A; |
|
526 |
- padding: 20px; |
|
527 |
- border-radius: 10px; |
|
528 |
- margin-bottom: 20px; |
|
529 |
- .buttons{gap: 5px; margin-top: 0;} |
|
530 |
- .sch-form-wrap{margin-bottom: 0;} |
|
531 |
- } |
|
532 |
- form{ |
|
533 |
- |
|
841 |
+ } |
|
842 |
+ |
|
843 |
+ p.require { |
|
844 |
+ text-align: right; |
|
845 |
+ |
|
846 |
+ img { |
|
847 |
+ margin-top: 9px; |
|
848 |
+ } |
|
849 |
+ } |
|
850 |
+ |
|
851 |
+ .card-body { |
|
852 |
+ .flex { |
|
853 |
+ display: flex; |
|
854 |
+ align-items: center; |
|
855 |
+ } |
|
856 |
+ .align-top{align-items: flex-start;} |
|
857 |
+ .flex.sb { |
|
858 |
+ justify-content: space-between; |
|
859 |
+ } |
|
860 |
+ |
|
861 |
+ .card-title { |
|
862 |
+ margin-bottom: 20px; |
|
863 |
+ } |
|
864 |
+ |
|
865 |
+ .sub { |
|
866 |
+ margin-right: 20px; |
|
867 |
+ |
|
868 |
+ img { |
|
869 |
+ width: 50px; |
|
870 |
+ height: 50px; |
|
871 |
+ } |
|
872 |
+ |
|
873 |
+ .date { |
|
874 |
+ margin-left: 10px; |
|
875 |
+ } |
|
876 |
+ } |
|
877 |
+ |
|
878 |
+ .sch-wrap { |
|
879 |
+ border: 1px solid #213F9A; |
|
880 |
+ padding: 20px; |
|
881 |
+ border-radius: 10px; |
|
882 |
+ margin-bottom: 20px; |
|
883 |
+ |
|
884 |
+ .buttons { |
|
885 |
+ gap: 5px; |
|
886 |
+ margin-top: 0; |
|
887 |
+ } |
|
888 |
+ |
|
889 |
+ .sch-form-wrap { |
|
890 |
+ margin-bottom: 0; |
|
891 |
+ } |
|
892 |
+ } |
|
893 |
+ .sch-form-wrap.search { |
|
894 |
+ position: relative; |
|
895 |
+ flex-direction: column; |
|
896 |
+ background-color: #EFF1FA; |
|
897 |
+ padding: 20px; |
|
898 |
+ border-radius: 10px; |
|
899 |
+ margin-right: 20px; |
|
900 |
+ width: 420px; |
|
901 |
+ height: 78rem; |
|
902 |
+ .sidemenu{max-height: 66rem; overflow-y: auto;} |
|
903 |
+ details{ |
|
904 |
+ width: 100%; |
|
905 |
+ summary{ |
|
906 |
+ border: 0; |
|
907 |
+ align-items: center; |
|
908 |
+ justify-content: flex-start; |
|
909 |
+ gap: 5px; |
|
910 |
+ .icon{display: block;} |
|
911 |
+ p{color: #213F9A; font-weight: 700; margin-right: 10px;} |
|
912 |
+ } |
|
913 |
+ .arrow { |
|
914 |
+ margin-right: 10px; |
|
915 |
+ } |
|
916 |
+ li{ |
|
917 |
+ margin-left: 40px; |
|
918 |
+ a{ |
|
919 |
+ justify-content: flex-start; |
|
920 |
+ align-items: center; |
|
921 |
+ img{margin-right: 10px;} |
|
922 |
+ } |
|
923 |
+ } |
|
924 |
+ |
|
925 |
+ } |
|
926 |
+ details[open]>summary { |
|
927 |
+ |
|
928 |
+ } |
|
929 |
+ .buttons{position: absolute; bottom: 40px;} |
|
930 |
+ .input-group{ |
|
931 |
+ display: block; |
|
932 |
+ margin-bottom: 20px; |
|
933 |
+ } |
|
934 |
+ table{ |
|
935 |
+ width: 100%; |
|
936 |
+ thead{ |
|
937 |
+ |
|
938 |
+ th{ |
|
939 |
+ background-color: #C7CFE3; |
|
940 |
+ position: sticky; |
|
941 |
+ top: -1px; |
|
942 |
+ border: 1px solid #C7CFE3; |
|
943 |
+ border-right: 1px solid #C7CFE3; |
|
944 |
+ } |
|
945 |
+ } |
|
946 |
+ } |
|
947 |
+ .table-scroll { |
|
948 |
+ max-height: max-content; /* 원하는 높이 설정 */ |
|
949 |
+ overflow-y: auto; |
|
950 |
+} |
|
951 |
+ } |
|
952 |
+ form { |
|
953 |
+ width: 100%; |
|
534 | 954 |
border: 1px solid #C7CFE3; |
535 |
- border-radius: 10px; overflow: hidden;} |
|
536 |
- .col-12{ |
|
955 |
+ border-radius: 10px; |
|
956 |
+ overflow: hidden; |
|
957 |
+ .second-label{ |
|
958 |
+ background-color: #fff !important; |
|
959 |
+ } |
|
960 |
+ |
|
961 |
+ } |
|
962 |
+ form.salary{ |
|
963 |
+ display: flex; |
|
964 |
+ .yearsalary{width: 100%;} |
|
965 |
+ } |
|
966 |
+ .col-12 { |
|
967 |
+ min-height: 4rem; |
|
537 | 968 |
width: 100%; |
538 | 969 |
display: flex; |
539 | 970 |
border-bottom: 1px solid #C7CFE3; |
540 |
- label{width: 140px ;background: #EFF1FA; font-weight: 600; font-size: 16px; text-align: center; line-height: 70px; flex-shrink: 0; position: relative;} |
|
541 |
- p.require{ |
|
542 |
- position: absolute; |
|
543 |
- right: 37px; |
|
544 |
- top: 23px; |
|
971 |
+ |
|
972 |
+ label { |
|
973 |
+ width: 140px; |
|
974 |
+ background: #EFF1FA; |
|
975 |
+ font-weight: 600; |
|
976 |
+ font-size: 16px; |
|
977 |
+ text-align: center; |
|
978 |
+ flex-shrink: 0; |
|
979 |
+ position: relative; |
|
980 |
+ display: flex; |
|
981 |
+ border-right: 1px solid #C7CFE3; |
|
982 |
+ align-items: center; |
|
983 |
+ justify-content: center; |
|
984 |
+ |
|
985 |
+ p { |
|
986 |
+ position: relative; |
|
987 |
+ } |
|
988 |
+ |
|
989 |
+ button { |
|
990 |
+ margin-left: 5px; |
|
991 |
+ } |
|
545 | 992 |
} |
546 |
- .d-flex{display: flex; flex-shrink: 0;} |
|
547 |
- select, input{margin: 15px 10px; border-color: #DDDDDD; height: var(--tk-input-h-sm);} |
|
548 |
- .form-control[readonly]{background-color: #EFF1FA;} |
|
549 |
- .invalid-feedback{color: #E92727; font-size: 13px;} |
|
550 |
- .box{ |
|
551 |
- margin: 15px 10px; |
|
552 |
- input{margin: 0;} |
|
553 |
- p{margin-top: 5px;} |
|
554 |
- } |
|
555 |
- .approval-container{ |
|
556 |
- display: flex; |
|
557 |
- flex-direction: column; |
|
993 |
+ |
|
994 |
+ p.require { |
|
995 |
+ position: absolute; |
|
996 |
+ right: -13px; |
|
997 |
+ top: 0; |
|
998 |
+ } |
|
999 |
+ |
|
1000 |
+ .d-flex { |
|
1001 |
+ display: flex; |
|
1002 |
+ flex-shrink: 0; |
|
1003 |
+ align-items: center; |
|
1004 |
+ } |
|
1005 |
+ |
|
1006 |
+ select, |
|
1007 |
+ input { |
|
1008 |
+ margin: 9px 10px; |
|
1009 |
+ border-color: #DDDDDD; |
|
1010 |
+ height: var(--tk-input-h-sm); |
|
1011 |
+ } |
|
1012 |
+ |
|
1013 |
+ .form-control[readonly] { |
|
1014 |
+ background-color: #EFF1FA; |
|
1015 |
+ } |
|
1016 |
+ |
|
1017 |
+ .invalid-feedback { |
|
1018 |
+ color: #E92727; |
|
1019 |
+ font-size: 13px; |
|
1020 |
+ } |
|
1021 |
+ |
|
1022 |
+ .box { |
|
1023 |
+ margin: 15px 10px; |
|
1024 |
+ |
|
1025 |
+ input { |
|
1026 |
+ margin: 0; |
|
1027 |
+ } |
|
1028 |
+ |
|
1029 |
+ p { |
|
1030 |
+ margin-top: 5px; |
|
1031 |
+ } |
|
1032 |
+ } |
|
1033 |
+ |
|
1034 |
+ .approval-container { |
|
1035 |
+ display: flex; |
|
1036 |
+ flex-direction: column; |
|
558 | 1037 |
gap: 8px; |
559 | 1038 |
margin: 15px 10px; |
560 |
- .addapproval{ |
|
1039 |
+ |
|
1040 |
+ .addapproval { |
|
561 | 1041 |
gap: 5px; |
562 | 1042 |
align-items: center; |
563 |
- .form-control, .form-select{margin: 0 ;} |
|
564 |
- .delete-button{margin-left: 10px;} |
|
1043 |
+ |
|
1044 |
+ .form-control, |
|
1045 |
+ .form-select { |
|
1046 |
+ margin: 0; |
|
1047 |
+ flex-shrink: 0; |
|
1048 |
+ } |
|
1049 |
+ |
|
1050 |
+ .delete-button { |
|
1051 |
+ margin-left: 10px; |
|
1052 |
+ } |
|
1053 |
+ |
|
1054 |
+ label { |
|
1055 |
+ width: 95px; |
|
1056 |
+ line-height: 30px; |
|
1057 |
+ background-color: #333333; |
|
1058 |
+ color: #fff; |
|
1059 |
+ border-radius: 5px; |
|
1060 |
+ } |
|
565 | 1061 |
} |
566 |
- } |
|
567 |
- |
|
568 |
- |
|
569 |
- |
|
1062 |
+ } |
|
1063 |
+ |
|
1064 |
+ .tbl-wrap { |
|
1065 |
+ width: 100%; |
|
1066 |
+ |
|
1067 |
+ table { |
|
1068 |
+ thead { |
|
1069 |
+ tr{ |
|
1070 |
+ th { |
|
1071 |
+ border-right: 1px solid #C7CFE3; |
|
1072 |
+ border-bottom: 1px solid #C7CFE3; |
|
1073 |
+ |
|
1074 |
+ } |
|
1075 |
+ |
|
1076 |
+ th:last-child { |
|
1077 |
+ border-right: 0; |
|
1078 |
+ } |
|
1079 |
+ } |
|
1080 |
+ |
|
1081 |
+ } |
|
1082 |
+ |
|
1083 |
+ tbody { |
|
1084 |
+ td { |
|
1085 |
+ border-right: 1px solid #C7CFE3; |
|
1086 |
+ |
|
1087 |
+ input { |
|
1088 |
+ margin: 0; |
|
1089 |
+ } |
|
1090 |
+ |
|
1091 |
+ |
|
1092 |
+ } |
|
1093 |
+ |
|
1094 |
+ td:last-child { |
|
1095 |
+ border-right: 0; |
|
1096 |
+ } |
|
1097 |
+ |
|
1098 |
+ tr:last-child td { |
|
1099 |
+ border-bottom: 0; |
|
1100 |
+ } |
|
1101 |
+ |
|
1102 |
+ } |
|
1103 |
+ } |
|
1104 |
+ |
|
1105 |
+ } |
|
1106 |
+ |
|
1107 |
+ |
|
570 | 1108 |
} |
571 |
- .col-12.return{ |
|
572 |
- .form-label{background-color: #FBC1C1;} |
|
573 |
- .form-control{background-color: #FFF2F2;} |
|
1109 |
+ .col-12.form-title{justify-content: center; background-color: #C7CFE3; font-weight: 600; |
|
1110 |
+ align-items: center; gap: 10px; |
|
1111 |
+ button{ |
|
1112 |
+ |
|
1113 |
+ } |
|
574 | 1114 |
} |
575 |
- .border-x{border: 0;} |
|
576 |
- .buttons{display: flex; gap: 10px; justify-content: end; margin-top:5rem; |
|
577 |
- .btn-red{border-color: #E92727; color: #E92727; background-color: #EFF1FA;} |
|
578 |
- |
|
1115 |
+ .col-12.return { |
|
1116 |
+ background-color: #FFF2F2 !important; |
|
1117 |
+ .form-label { |
|
1118 |
+ background-color: #FBC1C1; |
|
1119 |
+ } |
|
1120 |
+ |
|
1121 |
+ .form-control { |
|
1122 |
+ background-color: transparent !important; |
|
1123 |
+ border-color: transparent !important; |
|
1124 |
+ } |
|
579 | 1125 |
} |
580 |
- .hyuga{ |
|
581 |
- label{line-height: 40rem;} |
|
582 |
- input.textarea{min-height: 40rem;} |
|
583 |
- } |
|
584 |
- .chuljang{ |
|
585 |
- label{line-height: 17rem;} |
|
586 |
- input.textarea{min-height: 17rem;} |
|
587 |
- } |
|
588 |
- .form-card{ |
|
1126 |
+ |
|
1127 |
+ .border-x { |
|
1128 |
+ border: 0; |
|
1129 |
+ } |
|
1130 |
+ |
|
1131 |
+ .buttons { |
|
1132 |
+ display: flex; |
|
1133 |
+ gap: 10px; |
|
1134 |
+ justify-content: end; |
|
1135 |
+ margin-top: 5rem; |
|
1136 |
+ |
|
1137 |
+ .btn-red { |
|
1138 |
+ border-color: #E92727; |
|
1139 |
+ color: #E92727; |
|
1140 |
+ background-color: #EFF1FA; |
|
1141 |
+ } |
|
1142 |
+ |
|
1143 |
+ } |
|
1144 |
+ |
|
1145 |
+ .hyuga { |
|
1146 |
+ label { |
|
1147 |
+ line-height: 40rem; |
|
1148 |
+ } |
|
1149 |
+ |
|
1150 |
+ input.textarea { |
|
1151 |
+ min-height: 40rem; |
|
1152 |
+ } |
|
1153 |
+ } |
|
1154 |
+ |
|
1155 |
+ .chuljang { |
|
1156 |
+ label { |
|
1157 |
+ line-height: 17rem; |
|
1158 |
+ } |
|
1159 |
+ |
|
1160 |
+ input.textarea { |
|
1161 |
+ min-height: 17rem; |
|
1162 |
+ } |
|
1163 |
+ } |
|
1164 |
+ |
|
1165 |
+ .form-card { |
|
589 | 1166 |
position: relative; |
590 | 1167 |
border: 1px solid #CCCCCC; |
591 | 1168 |
border-radius: 10px; |
592 | 1169 |
padding: 30px; |
593 |
- h1{margin: 18px 0 48px 0; text-align: center;} |
|
1170 |
+ |
|
1171 |
+ h1 { |
|
1172 |
+ margin: 18px 0 48px 0; |
|
1173 |
+ text-align: center; |
|
1174 |
+ } |
|
1175 |
+ |
|
1176 |
+ .hyuga { |
|
1177 |
+ label { |
|
1178 |
+ line-height: 40rem; |
|
1179 |
+ } |
|
1180 |
+ |
|
1181 |
+ input.textarea { |
|
1182 |
+ min-height: 40rem; |
|
1183 |
+ } |
|
1184 |
+ } |
|
1185 |
+ |
|
1186 |
+ .chuljang { |
|
1187 |
+ label { |
|
1188 |
+ line-height: 17rem; |
|
1189 |
+ } |
|
1190 |
+ |
|
1191 |
+ input.textarea { |
|
1192 |
+ min-height: 17rem; |
|
1193 |
+ } |
|
1194 |
+ } |
|
1195 |
+ |
|
1196 |
+ .tbl2 { |
|
1197 |
+ width: 30rem; |
|
1198 |
+ |
|
1199 |
+ table { |
|
1200 |
+ .thead { |
|
1201 |
+ .th { |
|
1202 |
+ writing-mode: vertical-rl; |
|
1203 |
+ padding: 0; |
|
1204 |
+ width: 50px; |
|
1205 |
+ } |
|
1206 |
+ } |
|
1207 |
+ } |
|
1208 |
+ } |
|
1209 |
+ |
|
1210 |
+ .approval-box { |
|
1211 |
+ position: absolute; |
|
1212 |
+ right: 30px; |
|
1213 |
+ top: 30px; |
|
1214 |
+ } |
|
1215 |
+ |
|
1216 |
+ |
|
1217 |
+ |
|
1218 |
+ |
|
1219 |
+ } |
|
1220 |
+ |
|
1221 |
+ .detail { |
|
594 | 1222 |
input[readonly] { |
595 | 1223 |
border-color: #fff; |
596 | 1224 |
background-color: #fff !important; |
597 | 1225 |
cursor: context-menu; |
598 |
- } |
|
599 |
- .hyuga{ |
|
600 |
- label{line-height: 40rem;} |
|
601 |
- input.textarea{min-height: 40rem;} |
|
602 |
- } |
|
603 |
- .chuljang{ |
|
604 |
- label{line-height: 17rem;} |
|
605 |
- input.textarea{min-height: 17rem;} |
|
606 |
- } |
|
607 |
- .tbl2{ |
|
608 |
- width: 30rem; |
|
609 |
- table{ |
|
610 |
- .thead{ |
|
611 |
- .th{ |
|
612 |
- writing-mode: vertical-rl; padding: 0; width: 50px; |
|
613 |
- } |
|
614 |
- } |
|
615 |
- } |
|
616 |
- } |
|
617 |
- .approval-box{position: absolute; right: 30px; top: 30px;} |
|
618 |
- |
|
619 |
- |
|
620 |
- |
|
621 |
- |
|
1226 |
+ } |
|
622 | 1227 |
} |
623 |
- .tbl2{ |
|
624 |
- table{ |
|
1228 |
+ |
|
1229 |
+ .tbl2 { |
|
1230 |
+ table { |
|
625 | 1231 |
table-layout: fixed; |
626 | 1232 |
margin-bottom: 10px; |
627 |
- .thead{ |
|
628 |
- .th{background-color: #C7CFE3; } |
|
629 |
- td{background-color: #EFF1FA; height: 40px; font-weight: 700;} |
|
630 |
- } |
|
631 |
- td{padding: 0; border: 1px solid #C7CFE3; } |
|
1233 |
+ |
|
1234 |
+ .thead { |
|
1235 |
+ .th { |
|
1236 |
+ background-color: #C7CFE3; |
|
1237 |
+ } |
|
1238 |
+ |
|
1239 |
+ td { |
|
1240 |
+ background-color: #EFF1FA; |
|
1241 |
+ height: 40px; |
|
1242 |
+ font-weight: 700; |
|
1243 |
+ } |
|
1244 |
+ } |
|
1245 |
+ |
|
1246 |
+ td { |
|
1247 |
+ padding: 0; |
|
1248 |
+ border: 1px solid #C7CFE3; |
|
1249 |
+ } |
|
632 | 1250 |
|
633 | 1251 |
} |
634 | 1252 |
} |
635 |
- .datepicker-conts{ |
|
636 |
- margin-bottom: 20px; |
|
637 |
- .datepicker-input{ |
|
638 |
- display: flex; |
|
639 |
- justify-content: end; |
|
640 |
- align-items: center; |
|
641 |
- gap: 5px; |
|
642 |
- .form-control{ |
|
643 |
- padding-right: 16px; |
|
644 |
- border-color: #C7CFE3; |
|
1253 |
+ .tbl3{ |
|
1254 |
+ th,td{border: 0; border-bottom: 0 !important;} |
|
1255 |
+ thead{ |
|
1256 |
+ background-color: #EFF1FA; |
|
1257 |
+ th{padding: 10px;} |
|
1258 |
+ } |
|
1259 |
+ tbody{ |
|
1260 |
+ td{padding: 0 10px; |
|
1261 |
+ .form-select{width: -webkit-fill-available;} |
|
645 | 1262 |
} |
646 |
- mark{background-color: transparent;} |
|
1263 |
+ tr:last-child td{border-bottom: 0;} |
|
647 | 1264 |
} |
648 | 1265 |
} |
649 | 1266 |
|
1267 |
+ |
|
1268 |
+ } |
|
1269 |
+ |
|
1270 |
+ .cost-statue { |
|
1271 |
+ border: 1px solid #213F9A; |
|
1272 |
+ border-radius: 10px; |
|
1273 |
+ display: flex; |
|
1274 |
+ margin-bottom: 10px; |
|
1275 |
+ justify-content: center; |
|
1276 |
+ align-items: center; |
|
1277 |
+ |
|
1278 |
+ .costtitle { |
|
1279 |
+ font-size: 18px; |
|
1280 |
+ font-weight: 700; |
|
1281 |
+ margin-right: 3rem; |
|
1282 |
+ } |
|
1283 |
+ |
|
1284 |
+ .d-flex { |
|
1285 |
+ display: flex; |
|
1286 |
+ gap: 20px; |
|
1287 |
+ } |
|
1288 |
+ |
|
1289 |
+ .col-12 { |
|
1290 |
+ display: flex; |
|
1291 |
+ align-items: center; |
|
1292 |
+ |
|
1293 |
+ p { |
|
1294 |
+ font-weight: 600; |
|
1295 |
+ } |
|
1296 |
+ |
|
1297 |
+ input { |
|
1298 |
+ width: 140px; |
|
1299 |
+ } |
|
1300 |
+ } |
|
650 | 1301 |
} |
651 | 1302 |
} |
652 | 1303 |
} |
653 | 1304 |
|
654 |
-.modal-overlay { |
|
655 |
- position: fixed; |
|
656 |
- top: 0; |
|
657 |
- left: 0; |
|
658 |
- width: 100%; |
|
659 |
- height: 100%; |
|
660 |
- background: rgba(0, 0, 0, 0.4); |
|
661 |
- display: flex; |
|
662 |
- align-items: center; |
|
663 |
- justify-content: center; |
|
664 |
- z-index: 1000; |
|
665 |
- } |
|
666 |
- |
|
667 |
- .modal-box { |
|
668 |
- background: white; |
|
669 |
- padding: 20px 30px; |
|
670 |
- border-radius: 8px; |
|
671 |
- text-align: center; |
|
672 |
- } |
|
673 |
- |
|
674 |
- .modal-box button { |
|
675 |
- margin: 10px; |
|
676 |
- padding: 8px 16px; |
|
677 |
- font-size: 14px; |
|
678 |
- } |
|
679 |
- |
|
680 |
- .modal-box .cancel { |
|
681 |
- background: #ccc; |
|
682 |
- } |
|
1305 |
+.primary { |
|
1306 |
+ background-color: #213F9A !important; |
|
1307 |
+} |
|
683 | 1308 |
|
684 |
- .primary{background-color: #213F9A;} |
|
685 |
- a:focus{outline: 0;} |
|
686 |
- *:focus{outline: 0;} |
|
1309 |
+a:focus { |
|
1310 |
+ outline: 0; |
|
1311 |
+} |
|
687 | 1312 |
|
688 |
- .file { |
|
689 |
- background-color: #EFF1FA; width: 120px; border: #213F9A 1px solid ; border-radius: 10px; padding: 7px 0; |
|
1313 |
+*:focus { |
|
1314 |
+ outline: 0; |
|
1315 |
+} |
|
1316 |
+ |
|
1317 |
+.file { |
|
1318 |
+ background-color: #EFF1FA; |
|
1319 |
+ width: 120px; |
|
1320 |
+ border: #213F9A 1px solid; |
|
1321 |
+ border-radius: 10px; |
|
1322 |
+ padding: 7px 0; |
|
690 | 1323 |
margin-top: 10px; |
691 |
- } |
|
692 |
- |
|
693 |
- .file-label { |
|
1324 |
+} |
|
1325 |
+ |
|
1326 |
+.file-label { |
|
694 | 1327 |
width: inherit !important; |
695 | 1328 |
display: flex; |
696 | 1329 |
gap: 5px; |
697 | 1330 |
justify-content: center; |
698 | 1331 |
line-height: 0 !important; |
699 | 1332 |
cursor: pointer; |
700 |
- p{color: #000;} |
|
1333 |
+align-items: center; |
|
1334 |
+ p { |
|
1335 |
+ color: #000; |
|
1336 |
+ } |
|
1337 |
+} |
|
1338 |
+ |
|
1339 |
+.file-name { |
|
1340 |
+ max-width: 150px; |
|
1341 |
+ white-space: nowrap; |
|
1342 |
+ overflow: hidden; |
|
1343 |
+ text-overflow: ellipsis; |
|
1344 |
+ display: inline-block; |
|
1345 |
+ /* or block depending on layout */ |
|
1346 |
+ vertical-align: middle; |
|
1347 |
+ /* optional */ |
|
1348 |
+} |
|
1349 |
+ |
|
1350 |
+input[type="file"] { |
|
1351 |
+ display: none; |
|
1352 |
+} |
|
1353 |
+input[type="date"]{ |
|
1354 |
+ width: 200px; |
|
1355 |
+ padding-right: 10px; |
|
1356 |
+} |
|
1357 |
+input[type="button"]{ |
|
1358 |
+ width: 200px; |
|
1359 |
+ background-color: #333; |
|
1360 |
+ color: #fff; |
|
1361 |
+ padding-right: 10px; |
|
1362 |
+} |
|
1363 |
+table { |
|
1364 |
+ thead{ |
|
1365 |
+ .toptitle{ |
|
1366 |
+ th{background-color: #B7C2ED !important;} |
|
1367 |
+ } |
|
1368 |
+ .middletitle{ |
|
1369 |
+ th{background-color: #DAE0F7 !important;} |
|
1370 |
+ } |
|
1371 |
+ th{ |
|
1372 |
+ p { |
|
1373 |
+ background-color: #E92727; |
|
1374 |
+ border-radius: 5px; |
|
1375 |
+ color: #fff; |
|
1376 |
+ } |
|
1377 |
+ |
|
1378 |
+ p.green { |
|
1379 |
+ background-color: #219A8C; |
|
1380 |
+ } |
|
1381 |
+ |
|
1382 |
+ p.blue { |
|
1383 |
+ background-color: #1D75E1; |
|
1384 |
+ } |
|
1385 |
+ } |
|
1386 |
+ } |
|
1387 |
+ |
|
1388 |
+ th, |
|
1389 |
+ td { |
|
1390 |
+ border: 1px solid #C7CFE3; |
|
1391 |
+ border-bottom: 1px solid #C7CFE3 !important; |
|
1392 |
+ } |
|
1393 |
+ tbody{ |
|
1394 |
+ tr{ |
|
1395 |
+ th{background-color: var(--tk-secondary-5) !important; font-weight: 700 !important;} |
|
1396 |
+ } |
|
1397 |
+ |
|
1398 |
+ } |
|
1399 |
+} |
|
1400 |
+ |
|
1401 |
+.chk-area{ |
|
1402 |
+ .form-check{ |
|
1403 |
+ display: flex; |
|
1404 |
+ align-items: center; |
|
1405 |
+ justify-content: center; |
|
1406 |
+ } |
|
1407 |
+ label{padding-left: 30px !important; line-height: 0;} |
|
1408 |
+ |
|
1409 |
+} |
|
1410 |
+label:focus, button:focus{ |
|
1411 |
+ outline: 0; |
|
1412 |
+} |
|
1413 |
+.input-radio{ |
|
1414 |
+ .chk-area{ |
|
1415 |
+ margin: 9px 0 9px 10px; |
|
1416 |
+ .form-check{ |
|
1417 |
+ label{width: auto !important; line-height: 0 !important; background-color: #fff !important; border-right: 0 !important;} |
|
1418 |
+ } |
|
1419 |
+ } |
|
1420 |
+} |
|
1421 |
+.popup-overlay { |
|
1422 |
+ position: fixed; |
|
1423 |
+ top: 0; |
|
1424 |
+ left: 0; |
|
1425 |
+ width: 100%; |
|
1426 |
+ height: 100%; |
|
1427 |
+ background-color: rgba(0, 0, 0, 0.5); |
|
1428 |
+ display: flex; |
|
1429 |
+ align-items: center; |
|
1430 |
+ justify-content: center; |
|
1431 |
+ z-index: 999; |
|
701 | 1432 |
} |
702 | 1433 |
|
703 |
- input[type="file"] { |
|
704 |
- display: none; |
|
705 |
- }(파일 끝에 줄바꿈 문자 없음) |
|
1434 |
+ .popup-content { |
|
1435 |
+ position: relative; |
|
1436 |
+ background: white; |
|
1437 |
+ padding: 20px; |
|
1438 |
+ border-radius: 10px; |
|
1439 |
+ |
|
1440 |
+ text-align: center; |
|
1441 |
+ .form-control{margin: 0 !important;} |
|
1442 |
+ .card-title { |
|
1443 |
+ text-align: left; |
|
1444 |
+ } |
|
1445 |
+ .ico-sch{ |
|
1446 |
+ right: 17px !important; |
|
1447 |
+ } |
|
1448 |
+ table{ |
|
1449 |
+ th,td{ |
|
1450 |
+ |
|
1451 |
+ border-right: 1px solid #C7CFE3 !important; |
|
1452 |
+ } |
|
1453 |
+ } |
|
1454 |
+ .close-btn{ |
|
1455 |
+ position: absolute; |
|
1456 |
+ top: 20px; |
|
1457 |
+ right: 20px; |
|
1458 |
+ } |
|
1459 |
+ .btn.hyuga{background-color: #1D75E1;} |
|
1460 |
+ .btn.chuljang{background-color: #219A8C;} |
|
1461 |
+ } |
|
1462 |
+ |
|
1463 |
+ .datepicker-conts { |
|
1464 |
+ margin-bottom: 20px; |
|
1465 |
+} |
|
1466 |
+.datepicker-input { |
|
1467 |
+ display: flex; |
|
1468 |
+ justify-content: end; |
|
1469 |
+ align-items: center; |
|
1470 |
+ gap: 5px; |
|
1471 |
+ |
|
1472 |
+ .form-control { |
|
1473 |
+ padding-right: 16px !important; |
|
1474 |
+ border-color: #C7CFE3; |
|
1475 |
+ } |
|
1476 |
+ |
|
1477 |
+ mark { |
|
1478 |
+ background-color: transparent; |
|
1479 |
+ } |
|
1480 |
+}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/scss/common/component/_input.scss
+++ client/resources/scss/common/component/_input.scss
... | ... | @@ -270,7 +270,7 @@ |
270 | 270 |
} |
271 | 271 |
&:focus { |
272 | 272 |
~ label { |
273 |
- @include focus($offset: .4rem, $color: $primary); |
|
273 |
+ // @include focus($offset: .4rem, $color: $primary); |
|
274 | 274 |
} |
275 | 275 |
} |
276 | 276 |
&:disabled { |
+++ client/views/component/Popup/BuseoPopup.vue
... | ... | @@ -0,0 +1,109 @@ |
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.department }}</td> | |
31 | + <td>{{ item.name }}</td> | |
32 | + <td> | |
33 | + <button | |
34 | + type="button" | |
35 | + class="btn sm secondary" | |
36 | + @click="selectPerson(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> | |
74 | + </div> | |
75 | +</template> | |
76 | +<script> | |
77 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
78 | + | |
79 | +export default { | |
80 | + data() { | |
81 | + return { | |
82 | + popuplistData: [ | |
83 | + { | |
84 | + department: '총무부', | |
85 | + name: '이영희', | |
86 | + }, | |
87 | + { | |
88 | + department: '재무부', | |
89 | + name: '이영희', | |
90 | + }, | |
91 | + ], | |
92 | + } | |
93 | + }, | |
94 | + components: { | |
95 | + SearchOutlined, CloseCircleFilled | |
96 | + }, | |
97 | + methods: { | |
98 | + selectPerson(item) { | |
99 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
100 | + }, | |
101 | + | |
102 | + } | |
103 | +} | |
104 | +</script> | |
105 | +<style scoped> | |
106 | +.popup-content { | |
107 | + width: 50%; | |
108 | +} | |
109 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/ChuljangListPopup.vue
... | ... | @@ -0,0 +1,145 @@ |
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="datepicker-input"> | |
10 | + <input | |
11 | + type="date" | |
12 | + class="form-control datepicker cal" | |
13 | + :value="startDate" | |
14 | + style="max-width: 200px;" | |
15 | + /> | |
16 | + <mark>~</mark> | |
17 | + <input | |
18 | + type="date" | |
19 | + class="form-control datepicker cal" | |
20 | + :value="endDate" | |
21 | + style="max-width: 200px;" | |
22 | + /> | |
23 | + </div> | |
24 | + <select name="" id="" class="form-select"> | |
25 | + <option value="">전체</option> | |
26 | + <option value="">출장지</option> | |
27 | + <option value="">출장목적</option> | |
28 | + </select> | |
29 | + <div class="sch-input"> | |
30 | + <input type="text" class="form-control" > | |
31 | + <button class="ico-sch"><SearchOutlined /></button> | |
32 | + </div> | |
33 | + </div> | |
34 | + </div> | |
35 | + | |
36 | + <!-- Table --> | |
37 | + <div class="tbl-wrap"> | |
38 | + <table id="myTable" class="tbl data"> | |
39 | + <!-- 동적으로 <th> 생성 --> | |
40 | + <thead> | |
41 | + <tr> | |
42 | + <th>출장지 </th> | |
43 | + <th>출장목적</th> | |
44 | + <th>기간</th> | |
45 | + <th>구분(사용처)</th> | |
46 | + <th>금액(사용금액)</th> | |
47 | + <th>지급여부</th> | |
48 | + <th>선택</th> | |
49 | + </tr> | |
50 | + </thead> | |
51 | + <!-- 동적으로 <td> 생성 --> | |
52 | + <tbody> | |
53 | + <tr v-for="(item, index) in popuplistData" :key="index"> | |
54 | + <td>{{ item.location }}</td> | |
55 | + <td>{{ item.purpose }}</td> | |
56 | + <td>{{ item.period }}</td> | |
57 | + <td>{{ item.category }}</td> | |
58 | + <td>{{ item.amount }}</td> | |
59 | + <td>{{ item.paid }}</td> | |
60 | + <td> | |
61 | + <button | |
62 | + type="button" | |
63 | + class="btn sm secondary" | |
64 | + @click="selectPerson(item)" | |
65 | + > | |
66 | + 선택 | |
67 | + </button> | |
68 | + </td> | |
69 | + </tr> | |
70 | + </tbody> | |
71 | + </table> | |
72 | + | |
73 | + </div> | |
74 | + <div class="pagination"> | |
75 | + <ul> | |
76 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
77 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" | |
78 | + @click="changePage(currentPage - 1)"> | |
79 | + < | |
80 | + </li> | |
81 | + | |
82 | + <!-- 페이지 번호 --> | |
83 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
84 | + @click="changePage(page)"> | |
85 | + {{ page }} | |
86 | + </li> | |
87 | + | |
88 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
89 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" | |
90 | + @click="changePage(currentPage + 1)"> | |
91 | + > | |
92 | + </li> | |
93 | + </ul> | |
94 | + </div> | |
95 | + <!-- End Table --> | |
96 | + </div> | |
97 | + </div> | |
98 | + <button @click="$emit('close')" class="close-btn"> | |
99 | + <CloseCircleFilled /> | |
100 | + </button> | |
101 | + </div> | |
102 | + </div> | |
103 | +</template> | |
104 | +<script> | |
105 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
106 | + | |
107 | +export default { | |
108 | + data() { | |
109 | + return { | |
110 | + popuplistData: [ | |
111 | + { | |
112 | + location: '서울', | |
113 | + purpose: '회의 참석', | |
114 | + period: '2025-05-10 ~ 2025-05-12', | |
115 | + category: '교통(기차)', | |
116 | + amount: '150,000', | |
117 | + paid: '지급', | |
118 | + }, { | |
119 | + location: '서울', | |
120 | + purpose: '회의 참석', | |
121 | + period: '2025-05-10 ~ 2025-05-12', | |
122 | + category: '점심식사', | |
123 | + amount: '150,000', | |
124 | + paid: '미지급', | |
125 | + }, | |
126 | + | |
127 | + ], | |
128 | + } | |
129 | + }, | |
130 | + components: { | |
131 | + SearchOutlined, CloseCircleFilled | |
132 | + }, | |
133 | + methods: { | |
134 | + selectPerson(item) { | |
135 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
136 | + }, | |
137 | + | |
138 | + } | |
139 | +} | |
140 | +</script> | |
141 | +<style scoped> | |
142 | +.popup-content { | |
143 | + width: 50%; | |
144 | +} | |
145 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/CodePopup.vue
... | ... | @@ -0,0 +1,118 @@ |
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 | + <select name="" id="" class="form-select"> | |
10 | + <option value="">전체</option> | |
11 | + <option value="">상위코드</option> | |
12 | + <option value="">상위코드명</option> | |
13 | + <option value="">코드</option> | |
14 | + <option value="">코드명</option> | |
15 | + </select> | |
16 | + <div class="sch-input"> | |
17 | + <input type="text" class="form-control"> | |
18 | + <button class="ico-sch"><SearchOutlined /></button> | |
19 | + </div> | |
20 | + </div> | |
21 | + </div> | |
22 | + | |
23 | + <!-- Table --> | |
24 | + <div class="tbl-wrap"> | |
25 | + <table id="myTable" class="tbl data"> | |
26 | + <!-- 동적으로 <th> 생성 --> | |
27 | + <thead> | |
28 | + <tr> | |
29 | + <th>상위코드 </th> | |
30 | + <th>상위코드명</th> | |
31 | + <th>코드</th> | |
32 | + <th>코드명</th> | |
33 | + <th>선택</th> | |
34 | + </tr> | |
35 | + </thead> | |
36 | + <!-- 동적으로 <td> 생성 --> | |
37 | + <tbody> | |
38 | + <tr v-for="(item, index) in popuplistData" :key="index"> | |
39 | + <td>{{ item.parentCode }}</td> | |
40 | + <td>{{ item.parentCodeName }}</td> | |
41 | + <td>{{ item.code }}</td> | |
42 | + <td>{{ item.codeName }}</td> | |
43 | + <td> | |
44 | + <button | |
45 | + type="button" | |
46 | + class="btn sm secondary" | |
47 | + @click="selectPerson(item)" | |
48 | + > | |
49 | + 선택 | |
50 | + </button> | |
51 | + </td> | |
52 | + </tr> | |
53 | + </tbody> | |
54 | + </table> | |
55 | + | |
56 | + </div> | |
57 | + <div class="pagination"> | |
58 | + <ul> | |
59 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
60 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" | |
61 | + @click="changePage(currentPage - 1)"> | |
62 | + < | |
63 | + </li> | |
64 | + | |
65 | + <!-- 페이지 번호 --> | |
66 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
67 | + @click="changePage(page)"> | |
68 | + {{ page }} | |
69 | + </li> | |
70 | + | |
71 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
72 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" | |
73 | + @click="changePage(currentPage + 1)"> | |
74 | + > | |
75 | + </li> | |
76 | + </ul> | |
77 | + </div> | |
78 | + <!-- End Table --> | |
79 | + </div> | |
80 | + </div> | |
81 | + <button @click="$emit('close')" class="close-btn"> | |
82 | + <CloseCircleFilled /> | |
83 | + </button> | |
84 | + </div> | |
85 | + </div> | |
86 | +</template> | |
87 | +<script> | |
88 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
89 | + | |
90 | +export default { | |
91 | + data() { | |
92 | + return { | |
93 | + popuplistData: [ | |
94 | + { | |
95 | + parentCode: '상위코드1', | |
96 | + parentCodeName: '상위코드명1', | |
97 | + code: '코드1', | |
98 | + codeName: '코드명1' | |
99 | + }, | |
100 | + ], | |
101 | + } | |
102 | + }, | |
103 | + components: { | |
104 | + SearchOutlined, CloseCircleFilled | |
105 | + }, | |
106 | + methods: { | |
107 | + selectPerson(item) { | |
108 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
109 | + }, | |
110 | + | |
111 | + } | |
112 | +} | |
113 | +</script> | |
114 | +<style scoped> | |
115 | +.popup-content { | |
116 | + width: 50%; | |
117 | +} | |
118 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/CorpCarPopup.vue
... | ... | @@ -0,0 +1,105 @@ |
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> | |
74 | + </div> | |
75 | +</template> | |
76 | +<script> | |
77 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
78 | + | |
79 | +export default { | |
80 | + data() { | |
81 | + return { | |
82 | + popuplistData: [ | |
83 | + { | |
84 | + category: 'ooo', | |
85 | + name: 'ooo허 oooo', | |
86 | + }, | |
87 | + ], | |
88 | + } | |
89 | + }, | |
90 | + components: { | |
91 | + SearchOutlined, CloseCircleFilled | |
92 | + }, | |
93 | + methods: { | |
94 | + selectCar(item) { | |
95 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
96 | + }, | |
97 | + | |
98 | + } | |
99 | +} | |
100 | +</script> | |
101 | +<style scoped> | |
102 | +.popup-content { | |
103 | + width: 50%; | |
104 | +} | |
105 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/CorpCardPopup.vue
... | ... | @@ -0,0 +1,102 @@ |
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> | |
72 | + </div> | |
73 | +</template> | |
74 | +<script> | |
75 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
76 | + | |
77 | +export default { | |
78 | + data() { | |
79 | + return { | |
80 | + popuplistData: [ | |
81 | + { | |
82 | + name: '법인카드1', | |
83 | + }, | |
84 | + ], | |
85 | + } | |
86 | + }, | |
87 | + components: { | |
88 | + SearchOutlined, CloseCircleFilled | |
89 | + }, | |
90 | + methods: { | |
91 | + selectCard(item) { | |
92 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
93 | + }, | |
94 | + | |
95 | + } | |
96 | +} | |
97 | +</script> | |
98 | +<style scoped> | |
99 | +.popup-content { | |
100 | + width: 50%; | |
101 | +} | |
102 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/HrPopup.vue
... | ... | @@ -0,0 +1,121 @@ |
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" placeholder="직원명"> | |
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 | + <th>이름</th> | |
26 | + <th>아이디</th> | |
27 | + <th>선택</th> | |
28 | + </tr> | |
29 | + </thead> | |
30 | + <!-- 동적으로 <td> 생성 --> | |
31 | + <tbody> | |
32 | + <tr v-for="(item, index) in popuplistData" :key="index"> | |
33 | + <td>{{ item.position }}</td> | |
34 | + <td>{{ item.role }}</td> | |
35 | + <td>{{ item.department }}</td> | |
36 | + <td>{{ item.name }}</td> | |
37 | + <td>{{ item.id }}</td> | |
38 | + <td> | |
39 | + <button | |
40 | + type="button" | |
41 | + class="btn sm secondary" | |
42 | + @click="selectPerson(item)" | |
43 | + > | |
44 | + 선택 | |
45 | + </button> | |
46 | + </td> | |
47 | + </tr> | |
48 | + </tbody> | |
49 | + </table> | |
50 | + | |
51 | + </div> | |
52 | + <div class="pagination"> | |
53 | + <ul> | |
54 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
55 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" | |
56 | + @click="changePage(currentPage - 1)"> | |
57 | + < | |
58 | + </li> | |
59 | + | |
60 | + <!-- 페이지 번호 --> | |
61 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
62 | + @click="changePage(page)"> | |
63 | + {{ page }} | |
64 | + </li> | |
65 | + | |
66 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
67 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" | |
68 | + @click="changePage(currentPage + 1)"> | |
69 | + > | |
70 | + </li> | |
71 | + </ul> | |
72 | + </div> | |
73 | + <!-- End Table --> | |
74 | + </div> | |
75 | + </div> | |
76 | + <button @click="$emit('close')" class="close-btn"> | |
77 | + <CloseCircleFilled /> | |
78 | + </button> | |
79 | + </div> | |
80 | + </div> | |
81 | +</template> | |
82 | +<script> | |
83 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
84 | + | |
85 | +export default { | |
86 | + data() { | |
87 | + return { | |
88 | + popuplistData: [ | |
89 | + { | |
90 | + position: '과장', | |
91 | + role: '인사관리', | |
92 | + department: '총무부', | |
93 | + name: '김철수', | |
94 | + id: 'admin', | |
95 | + }, | |
96 | + { | |
97 | + position: '대리', | |
98 | + role: '회계', | |
99 | + department: '재무부', | |
100 | + name: '이영희', | |
101 | + id: 'admin1', | |
102 | + }, | |
103 | + ], | |
104 | + } | |
105 | + }, | |
106 | + components: { | |
107 | + SearchOutlined, CloseCircleFilled | |
108 | + }, | |
109 | + methods: { | |
110 | + selectPerson(item) { | |
111 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
112 | + }, | |
113 | + | |
114 | + } | |
115 | +} | |
116 | +</script> | |
117 | +<style scoped> | |
118 | +.popup-content { | |
119 | + width: 50%; | |
120 | +} | |
121 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/PaySlipPopup.vue
... | ... | @@ -0,0 +1,196 @@ |
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 | + <div class="buttons" style="margin: 3rem 0 1rem 0;"> | |
7 | + <button type="submit" class="btn tertiary xsm">다운로드<VerticalAlignBottomOutlined /></button> | |
8 | + </div> | |
9 | + <div class="form-card "> | |
10 | + <h2>임금명세서</h2> | |
11 | + <p class="paydate">지급일 : 2025-04-10</p> | |
12 | + <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" | |
13 | + @submit.prevent="handleRegister" novalidate> | |
14 | + | |
15 | + <div class="col-12 "> | |
16 | + <div class="col-12 border-x"> | |
17 | + <label for="youremail" class="form-label ">성명<p class="require"><img :src="require" alt=""></p> | |
18 | + </label> | |
19 | + <input v-model="email" type="text" name="username" class="form-control" required readonly> | |
20 | + </div> | |
21 | + | |
22 | + <div class="col-12 border-x"> | |
23 | + <label for="yourPassword" class="form-label">입사일</label> | |
24 | + <input v-model="password" type="password" name="password" class="form-control" required readonly> | |
25 | + </div> | |
26 | + </div> | |
27 | + <div class="col-12 border-x"> | |
28 | + <div class="col-12 border-x"> | |
29 | + <label for="youremail" class="form-label">부서</label> | |
30 | + <input v-model="email" type="text" name="username" class="form-control" required readonly> | |
31 | + </div> | |
32 | + | |
33 | + <div class="col-12 border-x"> | |
34 | + <label for="yourPassword" class="form-label">직급</label> | |
35 | + <input v-model="password" type="password" name="password" class="form-control" required readonly> | |
36 | + </div> | |
37 | + </div> | |
38 | + | |
39 | + | |
40 | + | |
41 | + </form> | |
42 | + | |
43 | + <div class="tbl-wrap " style="margin-top: 1rem;"> | |
44 | + <table id="myTable" class="tbl data"> | |
45 | + <!-- 동적으로 <th> 생성 --> | |
46 | + <thead> | |
47 | + <tr class="toptitle"> | |
48 | + <th colspan="5">세부내역</th> | |
49 | + </tr> | |
50 | + <tr class="middletitle"> | |
51 | + <th colspan="3">지급</th> | |
52 | + <th colspan="2">공제</th> | |
53 | + </tr> | |
54 | + </thead> | |
55 | + <!-- 동적으로 <td> 생성 --> | |
56 | + <tbody> | |
57 | + <tr> | |
58 | + <th rowspan="7">매월 지급 </th> | |
59 | + <th>임금항목</th> | |
60 | + <th>지급 금액(원)</th> | |
61 | + <th>공제 항목</th> | |
62 | + <th>공제 금액(원)</th> | |
63 | + </tr> | |
64 | + <tr> | |
65 | + <td></td> | |
66 | + <td></td> | |
67 | + <td></td> | |
68 | + <td></td> | |
69 | + </tr> | |
70 | + <tr> | |
71 | + <td></td> | |
72 | + <td></td> | |
73 | + <td></td> | |
74 | + <td></td> | |
75 | + </tr> | |
76 | + <tr> | |
77 | + <td></td> | |
78 | + <td></td> | |
79 | + <td></td> | |
80 | + <td></td> | |
81 | + </tr> | |
82 | + <tr> | |
83 | + <td></td> | |
84 | + <td></td> | |
85 | + <td></td> | |
86 | + <td></td> | |
87 | + </tr> | |
88 | + <tr> | |
89 | + <td></td> | |
90 | + <td></td> | |
91 | + <td></td> | |
92 | + <td></td> | |
93 | + </tr> | |
94 | + | |
95 | + </tbody> | |
96 | + <tbody> | |
97 | + <tr> | |
98 | + <th rowspan="3">격월 또는 부정기 지급</th> | |
99 | + <td></td> | |
100 | + <td></td> | |
101 | + <td></td> | |
102 | + <td></td> | |
103 | + </tr> | |
104 | + <tr> | |
105 | + <td></td> | |
106 | + <td></td> | |
107 | + <td></td> | |
108 | + <td></td> | |
109 | + </tr> | |
110 | + <tr> | |
111 | + <td></td> | |
112 | + <td></td> | |
113 | + <td></td> | |
114 | + <td></td> | |
115 | + </tr> | |
116 | + <tr> | |
117 | + <th colspan="2">지급액 계</th> | |
118 | + <td></td> | |
119 | + <th>지금액 계</th> | |
120 | + <td></td> | |
121 | + </tr> | |
122 | + <tr> | |
123 | + <td colspan="3"></td> | |
124 | + <th> 실수령액(원)</th> | |
125 | + <td></td> | |
126 | + </tr> | |
127 | + </tbody> | |
128 | + </table> | |
129 | + <table id="myTable" class="tbl data" style="margin-top: 10px;"> | |
130 | + <!-- 동적으로 <th> 생성 --> | |
131 | + <thead> | |
132 | + <tr class="toptitle"> | |
133 | + <th colspan="3">세부내역</th> | |
134 | + </tr> | |
135 | + <tr> | |
136 | + <th>구분</th> | |
137 | + <th>산출식 또는 산출방법</th> | |
138 | + <th>지급액(원)</th> | |
139 | + </tr> | |
140 | + </thead> | |
141 | + <!-- 동적으로 <td> 생성 --> | |
142 | + <tbody> | |
143 | + <tr> | |
144 | + <td></td> | |
145 | + <td></td> | |
146 | + <td></td> | |
147 | + </tr> | |
148 | + | |
149 | + | |
150 | + </tbody> | |
151 | + | |
152 | + </table> | |
153 | + | |
154 | + | |
155 | + </div> | |
156 | + </div> | |
157 | + | |
158 | + </div> | |
159 | + </div> | |
160 | + <button @click="$emit('close')" class="close-btn"> | |
161 | + <CloseCircleFilled /> | |
162 | + </button> | |
163 | + </div> | |
164 | + </div> | |
165 | +</template> | |
166 | +<script> | |
167 | +import { SearchOutlined, CloseCircleFilled,VerticalAlignBottomOutlined } from '@ant-design/icons-vue'; | |
168 | + | |
169 | +export default { | |
170 | + data() { | |
171 | + return { | |
172 | + } | |
173 | + }, | |
174 | + components: { | |
175 | + SearchOutlined, CloseCircleFilled, VerticalAlignBottomOutlined | |
176 | + }, | |
177 | + methods: { | |
178 | + selectCar(item) { | |
179 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
180 | + }, | |
181 | + | |
182 | + } | |
183 | +} | |
184 | +</script> | |
185 | +<style scoped> | |
186 | +.popup-content { | |
187 | + width: 50%; | |
188 | + | |
189 | +} | |
190 | +h2{font-size: 35px; letter-spacing: 10px;} | |
191 | +table{width: 100%;} | |
192 | +.form-control { | |
193 | + border-color: #C7CFE3; | |
194 | +} | |
195 | +.paydate{text-align: end;} | |
196 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/Popup/ReturnPopup.vue
... | ... | @@ -0,0 +1,48 @@ |
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 | + <textarea name="" id="" class="form-control "></textarea> | |
8 | + <div class="buttons"> | |
9 | + <button class="btn primary" type="submit">등록</button> | |
10 | + <button class="btn tertiary" type="submit" @click="$emit('close')">취소</button> | |
11 | + </div> | |
12 | + </div> | |
13 | + </div> | |
14 | + <button @click="$emit('close')" class="close-btn"> | |
15 | + <CloseCircleFilled /> | |
16 | + </button> | |
17 | + </div> | |
18 | + </div> | |
19 | +</template> | |
20 | +<script> | |
21 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
22 | + | |
23 | +export default { | |
24 | + data() { | |
25 | + return { | |
26 | + } | |
27 | + }, | |
28 | + components: { | |
29 | + SearchOutlined, CloseCircleFilled | |
30 | + }, | |
31 | + methods: { | |
32 | + selectCar(item) { | |
33 | + this.$emit('select', item); // 부모에게 데이터 전달 | |
34 | + }, | |
35 | + | |
36 | + } | |
37 | +} | |
38 | +</script> | |
39 | +<style scoped> | |
40 | +.popup-content { | |
41 | + width: 50%; | |
42 | + | |
43 | +} | |
44 | +.form-control{ | |
45 | + border-color: #C7CFE3; | |
46 | + min-height: 20rem; | |
47 | + } | |
48 | +</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -14,17 +14,15 @@ |
14 | 14 |
import ChuljangList from '../pages/Employee/ChuljangList.vue'; |
15 | 15 |
import HyugaList from '../pages/Employee/HyugaList.vue'; |
16 | 16 |
import HyugaOk from '../pages/Employee/HyugaOk.vue'; |
17 |
-// 관리자 |
|
17 |
+// 결재관리 |
|
18 | 18 |
import approval from '../pages/Manager/approval/approval.vue'; |
19 | 19 |
import approvalList from '../pages/Manager/approval/approvalList.vue'; |
20 | 20 |
import approvalRequest from '../pages/Manager/approval/approvalRequest.vue'; |
21 |
-import HyugaInsert from '../pages/Manager/approval/HyugaInsert.vue'; |
|
22 |
-import HyugaDetail from '../pages/Manager/approval/HyugaDetail.vue'; |
|
23 |
-import ChuljangInsert from '../pages/Manager/approval/ChuljangInsert.vue'; |
|
24 |
-import ChuljangDetail from '../pages/Manager/approval/ChuljangDetail.vue'; |
|
25 | 21 |
import ChuljangPumui from '../pages/Manager/approval/ChuljangPumui.vue'; |
26 |
-import ChuljangPumuiDetail from '../pages/Manager/attendance/ChuljangPumuiDetail.vue'; |
|
27 | 22 |
import ChuljangBokmyeong from '../pages/Manager/approval/ChuljangBokmyeong.vue'; |
23 |
+ |
|
24 |
+//근태관리 |
|
25 |
+import ChuljangPumuiDetail from '../pages/Manager/attendance/ChuljangPumuiDetail.vue'; |
|
28 | 26 |
import ChuljangBokmyeongDetail from '../pages/Manager/attendance/ChuljangBokmyeongDetail.vue'; |
29 | 27 |
import ChuljangDetailAll from '../pages/Manager/attendance/ChuljangDetailAll.vue'; |
30 | 28 |
import attendance from '../pages/Manager/attendance/attendance.vue'; |
... | ... | @@ -32,12 +30,43 @@ |
32 | 30 |
import buseoAttendance from '../pages/Manager/attendance/buseoAttendance.vue'; |
33 | 31 |
import AttendanceDetail from '../pages/Manager/attendance/AttendanceDetail.vue'; |
34 | 32 |
import hyugaStatue from '../pages/Manager/attendance/hyugaStatue.vue'; |
33 |
+import HyugaInsert from '../pages/Manager/approval/HyugaInsert.vue'; |
|
34 |
+import HyugaDetail from '../pages/Manager/approval/HyugaDetail.vue'; |
|
35 |
+import BokmyeongInsert from '../pages/Manager/attendance/BokmyeongInsert.vue'; |
|
35 | 36 |
import ChuljangStatue from '../pages/Manager/attendance/ChuljangStatue.vue'; |
37 |
+import ChuljangInsert from '../pages/Manager/attendance/ChuljangInsert.vue'; |
|
36 | 38 |
import task from '../pages/Manager/task/task.vue'; |
39 |
+import projectStatue from '../pages/Manager/task/projectStatue.vue'; |
|
40 |
+import projectDetail from '../pages/Manager/task/projectDetail.vue'; |
|
41 |
+import meetingInsert from '../pages/Manager/task/meetingInsert.vue'; |
|
42 |
+import meetingDetail from '../pages/Manager/task/meetingDetail.vue'; |
|
43 |
+import projectInsert from '../pages/Manager/task/projectInsert.vue'; |
|
44 |
+import projectTuib from '../pages/Manager/task/projectTuib.vue'; |
|
45 |
+import projectTuibDetail from '../pages/Manager/task/projectTuibDetail.vue'; |
|
37 | 46 |
import financial from '../pages/Manager/financial/financial.vue'; |
47 |
+import salaryList from '../pages/Manager/financial/salaryList.vue'; |
|
48 |
+import employeeSalaryList from '../pages/Manager/financial/employeeSalaryList.vue'; |
|
49 |
+import employeeSalaryDetail from '../pages/Manager/financial/employeeSalaryDetail.vue'; |
|
50 |
+import employeeSalaryInsert from '../pages/Manager/financial/employeeSalaryInsert.vue'; |
|
51 |
+import ChuljangCostList from '../pages/Manager/financial/ChuljangCostList.vue'; |
|
52 |
+import MeetingCostList from '../pages/Manager/financial/MeetingCostList.vue'; |
|
38 | 53 |
import asset from '../pages/Manager/asset/asset.vue'; |
54 |
+import CarList from '../pages/Manager/asset/CarList.vue'; |
|
55 |
+import CardList from '../pages/Manager/asset/CardList.vue'; |
|
56 |
+import CarInfoManagement from '../pages/Manager/asset/CarInfoManagement.vue'; |
|
57 |
+import CardInfoManagement from '../pages/Manager/asset/CardInfoManagement.vue'; |
|
39 | 58 |
import hr from '../pages/Manager/hr/hr.vue'; |
59 |
+import hrSearch from '../pages/Manager/hr/hrSearch.vue'; |
|
60 |
+import hrManagement from '../pages/Manager/hr/hrManagement.vue'; |
|
61 |
+import hrDetail from '../pages/Manager/hr/hrDetail.vue'; |
|
62 |
+import hrInsert from '../pages/Manager/hr/hrInsert.vue'; |
|
63 |
+import buseoManagement from '../pages/Manager/hr/buseoManagement.vue'; |
|
40 | 64 |
import system from '../pages/Manager/system/system.vue'; |
65 |
+import userManagement from '../pages/Manager/system/userManagement.vue'; |
|
66 |
+import accessControlManagement from '../pages/Manager/system/accessControlManagement.vue'; |
|
67 |
+import commonCodeManagement from '../pages/Manager/system/commonCodeManagement.vue'; |
|
68 |
+import commonCodeInsert from '../pages/Manager/system/commonCodeInsert.vue'; |
|
69 |
+import commonCodeDetail from '../pages/Manager/system/commonCodeDetail.vue'; |
|
41 | 70 |
|
42 | 71 |
const routes = [ |
43 | 72 |
/* 메인화면 */ |
... | ... | @@ -81,7 +110,7 @@ |
81 | 110 |
{ path: '/HyugaDetail.page', name: 'HyugaDetail', component: HyugaDetail }, |
82 | 111 |
{ path: '/HyugaInsert.page', name: 'HyugaInsert', component: HyugaInsert }, |
83 | 112 |
{ path: '/ChuljangStatue.page', name: 'ChuljangStatue', component: ChuljangStatue }, |
84 |
- { path: '/ChuljangDetail.page', name: 'ChuljangDetail', component: ChuljangDetail }, |
|
113 |
+ { path: '/BokmyeongInsert.page', name: 'BokmyeongInsert', component: BokmyeongInsert }, |
|
85 | 114 |
{ path: '/ChuljangInsert.page', name: 'ChuljangInsert', component: ChuljangInsert }, |
86 | 115 |
{ path: '/ChuljangPumuiDetail.page', name: 'ChuljangPumuiDetail', component: ChuljangPumuiDetail }, |
87 | 116 |
{ path: '/ChuljangBokmyeongDetail.page', name: 'ChuljangBokmyeongDetail', component: ChuljangBokmyeongDetail }, |
... | ... | @@ -89,11 +118,56 @@ |
89 | 118 |
|
90 | 119 |
] |
91 | 120 |
}, //근태관리 |
92 |
- { path: '/task-management.page', name: 'task', component: task }, //업무관리 |
|
93 |
- { path: '/financial-management.page', name: 'financial', component: financial }, //재무관리 |
|
94 |
- { path: '/asset-management.page', name: 'asset', component: asset }, //자산관리 |
|
95 |
- { path: '/hr-management.page', name: 'hr', component: hr }, //인사관리 |
|
96 |
- { path: '/system-management.page', name: 'system', component: system }, //시스템관리 |
|
121 |
+ { path: '/task-management.page', name: 'task', component: task, |
|
122 |
+ children: [ |
|
123 |
+ { path: '/projectStatue.page', name: 'projectStatue', component: projectStatue }, |
|
124 |
+ { path: '/projectDetail.page', name: 'projectDetail', component: projectDetail }, |
|
125 |
+ { path: '/meetingInsert.page', name: 'meetingInsert', component: meetingInsert }, |
|
126 |
+ { path: '/meetingDetail.page', name: 'meetingDetail', component: meetingDetail }, |
|
127 |
+ { path: '/projectInsert.page', name: 'projectInsert', component: projectInsert }, |
|
128 |
+ { path: '/projectTuib.page', name: 'projectTuib', component: projectTuib }, |
|
129 |
+ { path: '/projectTuibDetail.page', name: 'projectTuibDetail', component: projectTuibDetail }, |
|
130 |
+ ] |
|
131 |
+ }, //업무관리 |
|
132 |
+ { path: '/financial-management.page', name: 'financial', component: financial, |
|
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 |
+ { path: '/asset-management.page', name: 'asset', component: asset, |
|
143 |
+ children: [ |
|
144 |
+ { path: '/CarList.page', name: 'CarList', component: CarList }, |
|
145 |
+ { path: '/CarInfoManagement.page', name: 'CarInfoManagement', component: CarInfoManagement }, |
|
146 |
+ { path: '/CardList.page', name: 'CardList', component: CardList }, |
|
147 |
+ { path: '/CardInfoManagement.page', name: 'CardInfoManagement', component: CardInfoManagement }, |
|
148 |
+ |
|
149 |
+ ] |
|
150 |
+ }, //자산관리 |
|
151 |
+ { path: '/hr-management.page', name: 'hr', component: hr, |
|
152 |
+ children: [ |
|
153 |
+ { path: '/hrSearch.page', name: 'hrSearch', component: hrSearch }, |
|
154 |
+ { path: '/hrManagement.page', name: 'hrManagement', component: hrManagement }, |
|
155 |
+ { path: '/hrDetail.page', name: 'hrDetail', component: hrDetail }, |
|
156 |
+ { path: '/hrInsert.page', name: 'hrInsert', component: hrInsert }, |
|
157 |
+ |
|
158 |
+ { path: '/buseoManagement.page', name: 'buseoManagement', component: buseoManagement }, |
|
159 |
+ ] |
|
160 |
+ }, //인사관리 |
|
161 |
+ { path: '/system-management.page', name: 'system', component: system, |
|
162 |
+ children:[ |
|
163 |
+ { path: '/userManagement.page', name: 'userManagement', component: userManagement }, |
|
164 |
+ { path: '/accessControlManagement.page', name: 'accessControlManagement', component: accessControlManagement }, |
|
165 |
+ { path: '/commonCodeManagement.page', name: 'commonCodeManagement', component: commonCodeManagement }, |
|
166 |
+ { path: '/commonCodeInsert.page', name: 'commonCodeInsert', component: commonCodeInsert }, |
|
167 |
+ { path: '/commonCodeDetail.page', name: 'commonCodeDetail', component: commonCodeDetail }, |
|
168 |
+ |
|
169 |
+ ] |
|
170 |
+ }, //시스템관리 |
|
97 | 171 |
]; |
98 | 172 |
|
99 | 173 |
|
--- client/views/pages/Manager/approval/ChuljangBokmyeong.vue
+++ client/views/pages/Manager/approval/ChuljangBokmyeong.vue
... | ... | @@ -9,92 +9,99 @@ |
9 | 9 |
<table class="tbl data"> |
10 | 10 |
<tbody> |
11 | 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> |
|
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> |
|
20 | 23 |
</tbody> |
21 | 24 |
|
22 | 25 |
</table> |
23 | 26 |
</div> |
24 |
- <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
27 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
25 | 28 |
<div class="col-12 "> |
26 | 29 |
<div class="col-12 border-x"> |
27 | 30 |
<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" id="youremail" readonly > |
|
31 |
+ <input v-model="email" type="text" name="username" class="form-control" readonly > |
|
29 | 32 |
</div> |
30 | 33 |
|
31 | 34 |
<div class="col-12 border-x"> |
32 | 35 |
<label for="yourPassword" class="form-label">이름</label> |
33 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" readonly placeholder="주식회사 테이큰 소프트"> |
|
36 |
+ <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="주식회사 테이큰 소프트"> |
|
34 | 37 |
</div> |
35 | 38 |
</div> |
36 | 39 |
<div class="col-12"> |
37 | 40 |
<div class="col-12 border-x"> |
38 | 41 |
<label for="youremail" class="form-label">부서</label> |
39 |
- <input v-model="email" type="text" name="username" class="form-control" id="youremail" readonly placeholder="과장"> |
|
42 |
+ <input v-model="email" type="text" name="username" class="form-control" readonly placeholder="과장"> |
|
40 | 43 |
</div> |
41 | 44 |
|
42 | 45 |
<div class="col-12 border-x"> |
43 | 46 |
<label for="yourPassword" class="form-label">직급</label> |
44 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" readonly placeholder="팀장"> |
|
47 |
+ <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="팀장"> |
|
45 | 48 |
</div> |
46 | 49 |
</div> |
47 | 50 |
<div class="col-12"> |
48 | 51 |
<label for="yourName" class="form-label">출장지</label> |
49 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly> |
|
52 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly> |
|
50 | 53 |
</div> |
51 | 54 |
<div class="col-12"> |
52 | 55 |
<label for="yourName" class="form-label">출장목적</label> |
53 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
56 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
54 | 57 |
</div> |
55 | 58 |
<div class="col-12"> |
56 | 59 |
<label for="yourName" class="form-label">동행자</label> |
57 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
60 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
58 | 61 |
</div> |
59 | 62 |
<div class="col-12 chuljang"> |
60 | 63 |
<label for="yourName" class="form-label">복명내용</label> |
61 |
- <input v-model="name" type="text" name="name" class="form-control textarea " id="yourName" readonly> |
|
64 |
+ <input v-model="name" type="text" name="name" class="form-control textarea " readonly> |
|
62 | 65 |
</div> |
63 | 66 |
<div class="col-12"> |
64 | 67 |
<label for="yourName" class="form-label">법인카드</label> |
65 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
68 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
66 | 69 |
</div> |
67 | 70 |
<div class="col-12"> |
68 | 71 |
<label for="yourName" class="form-label">법인차량</label> |
69 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
72 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
70 | 73 |
</div> |
71 | 74 |
<div class="col-12"> |
72 | 75 |
<label for="yourName" class="form-label">여비계산</label> |
73 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
76 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
74 | 77 |
</div> |
75 | 78 |
<div class="col-12 border-x"> |
76 | 79 |
<label for="yourName" class="form-label">복명 신청일</label> |
77 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly placeholder="2025-01-01"> |
|
80 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly placeholder="2025-01-01"> |
|
78 | 81 |
</div> |
79 | 82 |
|
80 | 83 |
|
81 | 84 |
</form> |
82 | 85 |
</div> |
83 |
- <div class="buttons"> |
|
84 |
- <button class="btn primary" type="submit">승인</button> |
|
85 |
- <button class="btn btn-red " type="submit">반려</button> |
|
86 |
- <button class="btn tertiary " type="submit">목록</button> |
|
87 |
- </div> |
|
86 |
+ <div class="buttons"> |
|
87 |
+ <button class="btn primary" type="submit">승인</button> |
|
88 |
+ <button class="btn btn-red" type="submit" @click="showPopup = true">반려</button> |
|
89 |
+ <button class="btn tertiary " type="submit">목록</button> |
|
90 |
+ </div> |
|
91 |
+ <ReturnPopup v-if="showPopup" @close="showPopup = false" /> |
|
92 |
+ |
|
88 | 93 |
|
89 | 94 |
</div> |
90 | 95 |
</div> |
91 | 96 |
</template> |
92 | 97 |
|
93 | 98 |
<script> |
99 |
+import ReturnPopup from '../../../component/Popup/ReturnPopup.vue'; |
|
94 | 100 |
export default { |
95 | 101 |
data() { |
96 | 102 |
const today = new Date().toISOString().split('T')[0]; |
97 | 103 |
return { |
104 |
+ showPopup: false, |
|
98 | 105 |
startDate: today, |
99 | 106 |
startTime: "09:00", // 기본 시작 시간 09:00 |
100 | 107 |
endDate: today, |
... | ... | @@ -102,6 +109,10 @@ |
102 | 109 |
category: "", |
103 | 110 |
dayCount: 1, |
104 | 111 |
reason: "", // 사유 |
112 |
+ approvers: [ |
|
113 |
+ { position: '', name: '', date: '' }, |
|
114 |
+ { position: '', name: '', date: '' }, |
|
115 |
+ ], |
|
105 | 116 |
listData: [ |
106 | 117 |
{ |
107 | 118 |
type: '연차', |
... | ... | @@ -120,6 +131,9 @@ |
120 | 131 |
}], |
121 | 132 |
}; |
122 | 133 |
}, |
134 |
+ components: { |
|
135 |
+ ReturnPopup |
|
136 |
+ }, |
|
123 | 137 |
computed: { |
124 | 138 |
// Pinia Store의 상태를 가져옵니다. |
125 | 139 |
loginUser() { |
... | ... | @@ -128,22 +142,11 @@ |
128 | 142 |
}, |
129 | 143 |
}, |
130 | 144 |
methods: { |
131 |
- // 폼 검증 메서드 |
|
132 |
- validateForm() { |
|
133 |
- // 필수 입력 필드 체크 |
|
134 |
- if ( |
|
135 |
- this.category && |
|
136 |
- this.startDate && |
|
137 |
- this.startTime && |
|
138 |
- this.endDate && |
|
139 |
- this.endTime && |
|
140 |
- this.dayCount > 0 && |
|
141 |
- this.reason.trim() !== "" |
|
142 |
- ) { |
|
143 |
- this.isFormValid = true; |
|
144 |
- } else { |
|
145 |
- this.isFormValid = false; |
|
146 |
- } |
|
145 |
+ hasAnyApprover() { |
|
146 |
+ return this.approvers.some( |
|
147 |
+ (approver) => |
|
148 |
+ approver.name?.trim() !== '' && approver.date?.trim() !== '' |
|
149 |
+ ); |
|
147 | 150 |
}, |
148 | 151 |
calculateDayCount() { |
149 | 152 |
const start = new Date(`${this.startDate}T${this.startTime}:00`); |
... | ... | @@ -170,32 +173,9 @@ |
170 | 173 |
} |
171 | 174 |
} |
172 | 175 |
|
173 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
174 | 176 |
}, |
175 |
- handleSubmit() { |
|
176 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
177 |
- if (this.isFormValid) { |
|
178 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
179 |
- alert("승인 요청이 완료되었습니다."); |
|
180 |
- // 추가 처리 로직 (API 요청 등) |
|
181 |
- } else { |
|
182 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
183 |
- } |
|
184 |
- }, |
|
185 | 177 |
|
186 | 178 |
|
187 |
- }, |
|
188 |
- mounted() { |
|
189 |
- // Load the saved form data when the page is loaded |
|
190 |
- this.loadFormData(); |
|
191 |
- }, |
|
192 |
- watch: { |
|
193 |
- startDate: 'calculateDayCount', |
|
194 |
- startTime: 'calculateDayCount', |
|
195 |
- endDate: 'calculateDayCount', |
|
196 |
- endTime: 'calculateDayCount', |
|
197 |
- reason: "validateForm", |
|
198 |
- category: 'category', |
|
199 | 179 |
}, |
200 | 180 |
}; |
201 | 181 |
</script> |
--- client/views/pages/Manager/approval/ChuljangDetail.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>과장</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 " :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" id="youremail" 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" id="yourPassword" 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" id="youremail" 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" id="yourPassword" 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" id="yourName" readonly> | |
50 | - </div> | |
51 | - <div class="col-12 hyuga"> | |
52 | - <label for="yourName" class="form-label">세부사항</label> | |
53 | - <input v-model="name" type="text" name="name" class="form-control textarea" id="yourName" 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 " id="yourName" readonly> | |
58 | - </div> | |
59 | - <div class="col-12 border-x" :class="{ return: isReturned }"> | |
60 | - <label for="yourName" class="form-label ">반려사유</label> | |
61 | - <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly > | |
62 | - </div> | |
63 | - | |
64 | - | |
65 | - </form> | |
66 | - </div> | |
67 | - <div class="buttons"> | |
68 | - <button class="btn btn-red" type="submit">신청취소</button> | |
69 | - <button class="btn secondary" type="submit"> {{ isReturned ? '재신청' : '수정' }}</button> | |
70 | - <button class="btn tertiary " type="submit">목록</button> | |
71 | - </div> | |
72 | - | |
73 | - </div> | |
74 | - </div> | |
75 | -</template> | |
76 | - | |
77 | -<script> | |
78 | -export default { | |
79 | - data() { | |
80 | - const today = new Date().toISOString().split('T')[0]; | |
81 | - return { | |
82 | - isReturned: true, | |
83 | - startDate: today, | |
84 | - startTime: "09:00", // 기본 시작 시간 09:00 | |
85 | - endDate: today, | |
86 | - endTime: "18:00", // 기본 종료 시간 18:00 | |
87 | - category: "", | |
88 | - dayCount: 1, | |
89 | - reason: "", // 사유 | |
90 | - listData: [ | |
91 | - { | |
92 | - type: '연차', | |
93 | - approvalType: '결재', | |
94 | - applicant: '홍길동', | |
95 | - period: '2025-05-10 ~ 2025-15-03', | |
96 | - requestDate: '2025-04-25', | |
97 | - status: '대기' | |
98 | - }, { | |
99 | - type: '반차', | |
100 | - approvalType: '전결', | |
101 | - applicant: '홍길동', | |
102 | - period: '2025-05-01 ~ 2025-05-03', | |
103 | - requestDate: '2025-04-25', | |
104 | - status: '승인' | |
105 | - }], | |
106 | - }; | |
107 | - }, | |
108 | - computed: { | |
109 | - // Pinia Store의 상태를 가져옵니다. | |
110 | - loginUser() { | |
111 | - const authStore = useAuthStore(); | |
112 | - return authStore.getLoginUser; | |
113 | - }, | |
114 | - }, | |
115 | - methods: { | |
116 | - // 폼 검증 메서드 | |
117 | - validateForm() { | |
118 | - // 필수 입력 필드 체크 | |
119 | - if ( | |
120 | - this.category && | |
121 | - this.startDate && | |
122 | - this.startTime && | |
123 | - this.endDate && | |
124 | - this.endTime && | |
125 | - this.dayCount > 0 && | |
126 | - this.reason.trim() !== "" | |
127 | - ) { | |
128 | - this.isFormValid = true; | |
129 | - } else { | |
130 | - this.isFormValid = false; | |
131 | - } | |
132 | - }, | |
133 | - calculateDayCount() { | |
134 | - const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
135 | - const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
136 | - | |
137 | - let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
138 | - | |
139 | - if (this.startDate !== this.endDate) { | |
140 | - // 시작일과 종료일이 다른경우 | |
141 | - const startDateObj = new Date(this.startDate); | |
142 | - const endDateObj = new Date(this.endDate); | |
143 | - const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
144 | - if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
145 | - this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
146 | - } else { | |
147 | - this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
148 | - } | |
149 | - } else { | |
150 | - // 시작일과 종료일이 같은 경우 | |
151 | - if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
152 | - this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
153 | - } else { | |
154 | - this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
155 | - } | |
156 | - } | |
157 | - | |
158 | - this.validateForm(); // dayCount 변경 후 폼 재검증 | |
159 | - }, | |
160 | - handleSubmit() { | |
161 | - this.validateForm(); // 제출 시 유효성 확인 | |
162 | - if (this.isFormValid) { | |
163 | - localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); | |
164 | - alert("승인 요청이 완료되었습니다."); | |
165 | - // 추가 처리 로직 (API 요청 등) | |
166 | - } else { | |
167 | - alert("모든 필드를 올바르게 작성해주세요."); | |
168 | - } | |
169 | - }, | |
170 | - | |
171 | - | |
172 | - }, | |
173 | - mounted() { | |
174 | - // Load the saved form data when the page is loaded | |
175 | - this.loadFormData(); | |
176 | - }, | |
177 | - watch: { | |
178 | - startDate: 'calculateDayCount', | |
179 | - startTime: 'calculateDayCount', | |
180 | - endDate: 'calculateDayCount', | |
181 | - endTime: 'calculateDayCount', | |
182 | - reason: "validateForm", | |
183 | - category: 'category', | |
184 | - }, | |
185 | -}; | |
186 | -</script> |
--- client/views/pages/Manager/approval/ChuljangPumui.vue
+++ client/views/pages/Manager/approval/ChuljangPumui.vue
... | ... | @@ -1,197 +1,184 @@ |
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> |
|
5 |
+ |
|
6 | 6 |
<div class="form-card"> |
7 | 7 |
<h1>출장품의서</h1> |
8 | 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> |
|
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> |
|
21 | 23 |
|
22 |
- </table> |
|
24 |
+ </tbody> |
|
25 |
+ |
|
26 |
+ </table> |
|
23 | 27 |
</div> |
24 |
- <form class="row g-3 needs-validation " :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" id="youremail" 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" id="yourPassword" 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" id="youremail" 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" id="yourPassword" 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" id="yourName" 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 " id="yourName" 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 " id="yourName" 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 " id="yourName" 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 " id="yourName" 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 " id="yourName" readonly> |
|
70 |
- </div> |
|
28 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" |
|
29 |
+ @submit.prevent="handleRegister" novalidate> |
|
30 |
+ <div class="col-12 "> |
|
71 | 31 |
<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" id="yourName" readonly placeholder="2025-01-01"> |
|
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> |
|
74 | 34 |
</div> |
75 |
- |
|
76 |
- |
|
77 |
- </form> |
|
78 |
- </div> |
|
79 |
- <div class="buttons"> |
|
80 |
- <button class="btn primary" type="submit">승인</button> |
|
81 |
- <button class="btn btn-red " type="submit">반려</button> |
|
82 |
- <button class="btn tertiary " type="submit">목록</button> |
|
83 |
- </div> |
|
84 | 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> |
|
85 | 88 |
</div> |
89 |
+ <div class="buttons"> |
|
90 |
+ <button class="btn primary" type="submit">승인</button> |
|
91 |
+ <button class="btn btn-red" type="submit" @click="showPopup = true">반려</button> |
|
92 |
+ <button class="btn tertiary " type="submit">목록</button> |
|
93 |
+ </div> |
|
94 |
+ <ReturnPopup v-if="showPopup" @close="showPopup = false" /> |
|
95 |
+ |
|
86 | 96 |
</div> |
97 |
+ </div> |
|
87 | 98 |
</template> |
88 | 99 |
|
89 | 100 |
<script> |
101 |
+import ReturnPopup from '../../../component/Popup/ReturnPopup.vue'; |
|
90 | 102 |
export default { |
91 | 103 |
data() { |
92 | 104 |
const today = new Date().toISOString().split('T')[0]; |
93 | 105 |
return { |
106 |
+ showPopup: false, |
|
94 | 107 |
startDate: today, |
95 | 108 |
startTime: "09:00", // 기본 시작 시간 09:00 |
96 | 109 |
endDate: today, |
97 | 110 |
endTime: "18:00", // 기본 종료 시간 18:00 |
98 |
- category: "", |
|
99 |
- dayCount: 1, |
|
111 |
+ category: "", |
|
112 |
+ dayCount: 1, |
|
100 | 113 |
reason: "", // 사유 |
114 |
+ approvers: [ |
|
115 |
+ { position: '', name: '', date: '' }, |
|
116 |
+ { position: '', name: '', date: '' }, |
|
117 |
+ ], |
|
101 | 118 |
listData: [ |
102 |
- { |
|
103 |
- type: '연차', |
|
104 |
- approvalType: '결재', |
|
105 |
- applicant: '홍길동', |
|
106 |
- period: '2025-05-10 ~ 2025-15-03', |
|
107 |
- requestDate: '2025-04-25', |
|
108 |
- status: '대기' |
|
109 |
- }, { |
|
110 |
- type: '반차', |
|
111 |
- approvalType: '전결', |
|
112 |
- applicant: '홍길동', |
|
113 |
- period: '2025-05-01 ~ 2025-05-03', |
|
114 |
- requestDate: '2025-04-25', |
|
115 |
- status: '승인' |
|
116 |
- }], |
|
119 |
+ { |
|
120 |
+ type: '연차', |
|
121 |
+ approvalType: '결재', |
|
122 |
+ applicant: '홍길동', |
|
123 |
+ period: '2025-05-10 ~ 2025-15-03', |
|
124 |
+ requestDate: '2025-04-25', |
|
125 |
+ status: '대기' |
|
126 |
+ }, { |
|
127 |
+ type: '반차', |
|
128 |
+ approvalType: '전결', |
|
129 |
+ applicant: '홍길동', |
|
130 |
+ period: '2025-05-01 ~ 2025-05-03', |
|
131 |
+ requestDate: '2025-04-25', |
|
132 |
+ status: '승인' |
|
133 |
+ }], |
|
117 | 134 |
}; |
118 | 135 |
}, |
136 |
+ components: { |
|
137 |
+ ReturnPopup |
|
138 |
+ }, |
|
119 | 139 |
computed: { |
120 |
- // Pinia Store의 상태를 가져옵니다. |
|
121 |
- loginUser() { |
|
122 |
- const authStore = useAuthStore(); |
|
123 |
- return authStore.getLoginUser; |
|
124 |
- }, |
|
125 | 140 |
}, |
126 | 141 |
methods: { |
127 |
- // 폼 검증 메서드 |
|
128 |
- validateForm() { |
|
129 |
- // 필수 입력 필드 체크 |
|
130 |
- if ( |
|
131 |
- this.category && |
|
132 |
- this.startDate && |
|
133 |
- this.startTime && |
|
134 |
- this.endDate && |
|
135 |
- this.endTime && |
|
136 |
- this.dayCount > 0 && |
|
137 |
- this.reason.trim() !== "" |
|
138 |
- ) { |
|
139 |
- this.isFormValid = true; |
|
140 |
- } else { |
|
141 |
- this.isFormValid = false; |
|
142 |
- } |
|
142 |
+ hasAnyApprover() { |
|
143 |
+ return this.approvers.some( |
|
144 |
+ (approver) => |
|
145 |
+ approver.name?.trim() !== '' && approver.date?.trim() !== '' |
|
146 |
+ ); |
|
143 | 147 |
}, |
144 | 148 |
calculateDayCount() { |
145 |
- const start = new Date(`${this.startDate}T${this.startTime}:00`); |
|
146 |
- const end = new Date(`${this.endDate}T${this.endTime}:00`); |
|
147 |
- |
|
148 |
- let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 |
|
149 |
- |
|
150 |
- if (this.startDate !== this.endDate) { |
|
151 |
- // 시작일과 종료일이 다른경우 |
|
152 |
- const startDateObj = new Date(this.startDate); |
|
153 |
- const endDateObj = new Date(this.endDate); |
|
154 |
- const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 |
|
155 |
- if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
156 |
- this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 |
|
157 |
- } else { |
|
158 |
- this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 |
|
159 |
- } |
|
160 |
- } else { |
|
161 |
- // 시작일과 종료일이 같은 경우 |
|
162 |
- if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
163 |
- this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 |
|
164 |
- } else { |
|
165 |
- this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 |
|
166 |
- } |
|
167 |
- } |
|
149 |
+ const start = new Date(`${this.startDate}T${this.startTime}:00`); |
|
150 |
+ const end = new Date(`${this.endDate}T${this.endTime}:00`); |
|
168 | 151 |
|
169 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
170 |
- }, |
|
171 |
- handleSubmit() { |
|
172 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
173 |
- if (this.isFormValid) { |
|
174 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
175 |
- alert("승인 요청이 완료되었습니다."); |
|
176 |
- // 추가 처리 로직 (API 요청 등) |
|
152 |
+ let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 |
|
153 |
+ |
|
154 |
+ if (this.startDate !== this.endDate) { |
|
155 |
+ // 시작일과 종료일이 다른경우 |
|
156 |
+ const startDateObj = new Date(this.startDate); |
|
157 |
+ const endDateObj = new Date(this.endDate); |
|
158 |
+ const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 |
|
159 |
+ if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
160 |
+ this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 |
|
161 |
+ } else { |
|
162 |
+ this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 |
|
163 |
+ } |
|
177 | 164 |
} else { |
178 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
165 |
+ // 시작일과 종료일이 같은 경우 |
|
166 |
+ if (this.startTime !== "09:00" || this.endTime !== "18:00") { |
|
167 |
+ this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 |
|
168 |
+ } else { |
|
169 |
+ this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 |
|
170 |
+ } |
|
179 | 171 |
} |
172 |
+ |
|
180 | 173 |
}, |
181 |
- |
|
182 |
- |
|
183 |
- }, |
|
184 |
- mounted() { |
|
185 |
- // Load the saved form data when the page is loaded |
|
186 |
- this.loadFormData(); |
|
187 |
- }, |
|
188 |
- watch: { |
|
189 |
- startDate: 'calculateDayCount', |
|
190 |
- startTime: 'calculateDayCount', |
|
191 |
- endDate: 'calculateDayCount', |
|
192 |
- endTime: 'calculateDayCount', |
|
193 |
- reason: "validateForm", |
|
194 |
- category: 'category', |
|
174 |
+ |
|
175 |
+ |
|
176 |
+ |
|
195 | 177 |
}, |
196 | 178 |
}; |
197 | 179 |
</script> |
180 |
+<style scoped> |
|
181 |
+td p { |
|
182 |
+ width: 125px; |
|
183 |
+} |
|
184 |
+</style> |
--- client/views/pages/Manager/approval/HyugaDetail.vue
+++ client/views/pages/Manager/approval/HyugaDetail.vue
... | ... | @@ -21,65 +21,69 @@ |
21 | 21 |
|
22 | 22 |
</table> |
23 | 23 |
</div> |
24 |
- <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
24 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
25 | 25 |
<div class="col-12 "> |
26 | 26 |
<div class="col-12 border-x"> |
27 | 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" id="youremail" readonly > |
|
28 |
+ <input v-model="email" type="text" name="username" class="form-control" readonly > |
|
29 | 29 |
</div> |
30 | 30 |
|
31 | 31 |
<div class="col-12 border-x"> |
32 | 32 |
<label for="yourPassword" class="form-label">신청자</label> |
33 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" readonly placeholder="주식회사 테이큰 소프트"> |
|
33 |
+ <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="주식회사 테이큰 소프트"> |
|
34 | 34 |
</div> |
35 | 35 |
</div> |
36 | 36 |
<div class="col-12"> |
37 | 37 |
<div class="col-12 border-x"> |
38 | 38 |
<label for="youremail" class="form-label">부서</label> |
39 |
- <input v-model="email" type="text" name="username" class="form-control" id="youremail" readonly placeholder="과장"> |
|
39 |
+ <input v-model="email" type="text" name="username" class="form-control" readonly placeholder="과장"> |
|
40 | 40 |
</div> |
41 | 41 |
|
42 | 42 |
<div class="col-12 border-x"> |
43 | 43 |
<label for="yourPassword" class="form-label">직급</label> |
44 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" readonly placeholder="팀장"> |
|
44 |
+ <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="팀장"> |
|
45 | 45 |
</div> |
46 | 46 |
</div> |
47 | 47 |
<div class="col-12"> |
48 | 48 |
<label for="yourName" class="form-label">기간</label> |
49 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly> |
|
49 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly> |
|
50 | 50 |
</div> |
51 | 51 |
<div class="col-12 hyuga"> |
52 | 52 |
<label for="yourName" class="form-label">세부사항</label> |
53 |
- <input v-model="name" type="text" name="name" class="form-control textarea" id="yourName" readonly> |
|
53 |
+ <input v-model="name" type="text" name="name" class="form-control textarea" readonly> |
|
54 | 54 |
</div> |
55 | 55 |
<div class="col-12 "> |
56 | 56 |
<label for="yourName" class="form-label">신청일</label> |
57 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
57 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
58 | 58 |
</div> |
59 |
- <div class="col-12 border-x" :class="{ return: isReturned }"> |
|
59 |
+ <div class="col-12 border-x return" > |
|
60 | 60 |
<label for="yourName" class="form-label ">반려사유</label> |
61 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly > |
|
61 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly > |
|
62 | 62 |
</div> |
63 | 63 |
|
64 | 64 |
|
65 | 65 |
</form> |
66 | 66 |
</div> |
67 | 67 |
<div class="buttons"> |
68 |
- <button class="btn btn-red" type="submit">신청취소</button> |
|
69 |
- <button class="btn secondary" type="submit"> {{ isReturned ? '재신청' : '수정' }}</button> |
|
70 |
- <button class="btn tertiary " type="submit">목록</button> |
|
68 |
+ <button class="btn primary" type="button">승인</button> |
|
69 |
+ <button class="btn btn-red" type="button" @click="showPopup = true">반려</button> |
|
70 |
+ <button class="btn btn-red" type="button">신청취소</button> |
|
71 |
+ <button class="btn secondary" type="button">재신청</button> |
|
72 |
+ <button class="btn secondary" type="button">수정</button> |
|
73 |
+ <button class="btn tertiary " type="button">목록</button> |
|
71 | 74 |
</div> |
72 |
- |
|
75 |
+ <ReturnPopup v-if="showPopup" @close="showPopup = false"/> |
|
73 | 76 |
</div> |
74 | 77 |
</div> |
75 | 78 |
</template> |
76 | 79 |
|
77 | 80 |
<script> |
81 |
+import ReturnPopup from '../../../component/Popup/ReturnPopup.vue'; |
|
78 | 82 |
export default { |
79 | 83 |
data() { |
80 | 84 |
const today = new Date().toISOString().split('T')[0]; |
81 | 85 |
return { |
82 |
- isReturned: true, |
|
86 |
+ showPopup: false, |
|
83 | 87 |
startDate: today, |
84 | 88 |
startTime: "09:00", // 기본 시작 시간 09:00 |
85 | 89 |
endDate: today, |
... | ... | @@ -105,31 +109,14 @@ |
105 | 109 |
}], |
106 | 110 |
}; |
107 | 111 |
}, |
112 |
+ components: { |
|
113 |
+ ReturnPopup |
|
114 |
+ }, |
|
108 | 115 |
computed: { |
109 |
- // Pinia Store의 상태를 가져옵니다. |
|
110 |
- loginUser() { |
|
111 |
- const authStore = useAuthStore(); |
|
112 |
- return authStore.getLoginUser; |
|
113 |
- }, |
|
116 |
+ |
|
114 | 117 |
}, |
115 | 118 |
methods: { |
116 |
- // 폼 검증 메서드 |
|
117 |
- validateForm() { |
|
118 |
- // 필수 입력 필드 체크 |
|
119 |
- if ( |
|
120 |
- this.category && |
|
121 |
- this.startDate && |
|
122 |
- this.startTime && |
|
123 |
- this.endDate && |
|
124 |
- this.endTime && |
|
125 |
- this.dayCount > 0 && |
|
126 |
- this.reason.trim() !== "" |
|
127 |
- ) { |
|
128 |
- this.isFormValid = true; |
|
129 |
- } else { |
|
130 |
- this.isFormValid = false; |
|
131 |
- } |
|
132 |
- }, |
|
119 |
+ |
|
133 | 120 |
calculateDayCount() { |
134 | 121 |
const start = new Date(`${this.startDate}T${this.startTime}:00`); |
135 | 122 |
const end = new Date(`${this.endDate}T${this.endTime}:00`); |
... | ... | @@ -157,30 +144,12 @@ |
157 | 144 |
|
158 | 145 |
this.validateForm(); // dayCount 변경 후 폼 재검증 |
159 | 146 |
}, |
160 |
- handleSubmit() { |
|
161 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
162 |
- if (this.isFormValid) { |
|
163 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
164 |
- alert("승인 요청이 완료되었습니다."); |
|
165 |
- // 추가 처리 로직 (API 요청 등) |
|
166 |
- } else { |
|
167 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
168 |
- } |
|
169 |
- }, |
|
147 |
+ |
|
170 | 148 |
|
171 | 149 |
|
172 | 150 |
}, |
173 | 151 |
mounted() { |
174 |
- // Load the saved form data when the page is loaded |
|
175 |
- this.loadFormData(); |
|
176 | 152 |
}, |
177 |
- watch: { |
|
178 |
- startDate: 'calculateDayCount', |
|
179 |
- startTime: 'calculateDayCount', |
|
180 |
- endDate: 'calculateDayCount', |
|
181 |
- endTime: 'calculateDayCount', |
|
182 |
- reason: "validateForm", |
|
183 |
- category: 'category', |
|
184 |
- }, |
|
153 |
+ |
|
185 | 154 |
}; |
186 | 155 |
</script> |
--- client/views/pages/Manager/approval/HyugaInsert.vue
+++ client/views/pages/Manager/approval/HyugaInsert.vue
... | ... | @@ -7,19 +7,27 @@ |
7 | 7 |
<!-- Multi Columns Form --> |
8 | 8 |
<form class="row g-3 needs-validation" @submit.prevent="handleSubmit"> |
9 | 9 |
<div class="col-12"> |
10 |
- <label for="inputName5" class="form-label">유형<p class="require"><img :src="require" alt=""></p></label> |
|
11 |
- <select id="category" class="form-select" v-model="category" style="max-width: 200px;"> |
|
12 |
- <option selected>연차</option> |
|
13 |
- <option>반차</option> |
|
14 |
- <option>병가</option> |
|
15 |
- <option>경조</option> |
|
16 |
- <option>무급</option> |
|
17 |
- <option>공가</option> |
|
10 |
+ <label for="inputName5" class="form-label"> |
|
11 |
+ <p>유형 |
|
12 |
+ <p class="require"><img :src="require" alt=""></p> |
|
13 |
+ </p> |
|
14 |
+ </label> |
|
15 |
+ <select id="category" class="form-select" style="max-width: 200px;"> |
|
16 |
+ <option value="연차">연차</option> |
|
17 |
+ <option value="반차">반차</option> |
|
18 |
+ <option value="병가">병가</option> |
|
19 |
+ <option value="경조">경조</option> |
|
20 |
+ <option value="무급">무급</option> |
|
21 |
+ <option value="공가">공가</option> |
|
18 | 22 |
</select> |
19 | 23 |
</div> |
20 | 24 |
|
21 | 25 |
<div class="col-12"> |
22 |
- <label for="startDate" class="form-label">시작일<p class="require"><img :src="require" alt=""></p></label> |
|
26 |
+ <label for="startDate" class="form-label"> |
|
27 |
+ <p>시작일 |
|
28 |
+ <p class="require"><img :src="require" alt=""></p> |
|
29 |
+ </p> |
|
30 |
+ </label> |
|
23 | 31 |
<div class="d-flex gap-1"> |
24 | 32 |
<input type="date" class="form-control" id="startDate" v-model="startDate" /> |
25 | 33 |
<!-- 시간 선택을 위한 select 사용 --> |
... | ... | @@ -39,7 +47,11 @@ |
39 | 47 |
</div> |
40 | 48 |
|
41 | 49 |
<div class="col-12"> |
42 |
- <label for="endDate" class="form-label">종료일<p class="require"><img :src="require" alt=""></p></label> |
|
50 |
+ <label for="endDate" class="form-label"> |
|
51 |
+ <p>종료일 |
|
52 |
+ <p class="require"><img :src="require" alt=""></p> |
|
53 |
+ </p> |
|
54 |
+ </label> |
|
43 | 55 |
<div class="d-flex gap-1"> |
44 | 56 |
<input type="date" class="form-control" id="endDate" v-model="endDate" /> |
45 | 57 |
<!-- 종료 시간을 위한 select 사용 --> |
... | ... | @@ -66,11 +78,11 @@ |
66 | 78 |
<div class="col-12"> |
67 | 79 |
<label for="member" class="form-label"> |
68 | 80 |
승인자 |
69 |
- <button type="button" title="추가" @click="addApproval"> |
|
81 |
+ <button type="button" title="추가" @click="showPopup = true"> |
|
70 | 82 |
<PlusCircleFilled /> |
71 | 83 |
</button> |
72 | 84 |
</label> |
73 |
- |
|
85 |
+ <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval" /> |
|
74 | 86 |
<!-- 반복 렌더링되는 addapproval 항목 --> |
75 | 87 |
<div class="approval-container"> |
76 | 88 |
<div v-for="(approval, index) in approvals" :key="index" class="d-flex gap-2 addapproval mb-2"> |
... | ... | @@ -107,10 +119,12 @@ |
107 | 119 |
|
108 | 120 |
<script> |
109 | 121 |
import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; |
122 |
+import HrPopup from '../../../component/Popup/HrPopup.vue'; |
|
110 | 123 |
export default { |
111 | 124 |
data() { |
112 | 125 |
const today = new Date().toISOString().split('T')[0]; |
113 | 126 |
return { |
127 |
+ showPopup: false, |
|
114 | 128 |
approvals: [], |
115 | 129 |
require: "/client/resources/img/require.png", |
116 | 130 |
startDate: today, |
... | ... | @@ -123,7 +137,7 @@ |
123 | 137 |
}; |
124 | 138 |
}, |
125 | 139 |
components: { |
126 |
- PlusCircleFilled, CloseOutlined |
|
140 |
+ PlusCircleFilled, CloseOutlined, HrPopup |
|
127 | 141 |
}, |
128 | 142 |
computed: { |
129 | 143 |
// Pinia Store의 상태를 가져옵니다. |
... | ... | @@ -133,11 +147,12 @@ |
133 | 147 |
}, |
134 | 148 |
}, |
135 | 149 |
methods: { |
136 |
- addApproval() { |
|
150 |
+ addApproval(selectedUser) { |
|
137 | 151 |
this.approvals.push({ |
138 | 152 |
category: '결재', |
139 |
- name: '', |
|
153 |
+ name: selectedUser.name, // or other fields if needed |
|
140 | 154 |
}); |
155 |
+ this.showPopup = false; // 팝업 닫기 |
|
141 | 156 |
}, |
142 | 157 |
// 승인자 삭제 |
143 | 158 |
removeApproval(index) { |
--- client/views/pages/Manager/approval/approvalList.vue
+++ client/views/pages/Manager/approval/approvalList.vue
... | ... | @@ -7,14 +7,24 @@ |
7 | 7 |
<h3><img :src="h3icon" alt="">승인 대기</h3> |
8 | 8 |
<div class="input-group"> |
9 | 9 |
<select name="" id="" class="form-select"> |
10 |
- <option value="">년도</option> |
|
10 |
+ <option :value="currentYear">{{ currentYear }}년</option> |
|
11 |
+ <option value="all">전체</option> |
|
12 |
+ <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> |
|
13 |
+ {{ year }}년 |
|
14 |
+ </option> |
|
11 | 15 |
</select> |
12 | 16 |
<select name="" id="" class="form-select"> |
13 |
- <option value="">월</option> |
|
17 |
+ <option :value="currentMonth">{{ currentMonth }}월</option> |
|
18 |
+ <option value="all">전체</option> |
|
19 |
+ <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> |
|
20 |
+ {{ month }}월 |
|
21 |
+ </option> |
|
14 | 22 |
</select> |
15 | 23 |
<div class="sch-input"> |
16 |
- <input type="text" class="form-control"> |
|
17 |
- <button class="ico-sch"><SearchOutlined /></button> |
|
24 |
+ <input type="text" class="form-control" placeholder="신청자명"> |
|
25 |
+ <button class="ico-sch"> |
|
26 |
+ <SearchOutlined /> |
|
27 |
+ </button> |
|
18 | 28 |
</div> |
19 | 29 |
</div> |
20 | 30 |
</div> |
... | ... | @@ -30,18 +40,16 @@ |
30 | 40 |
<th>신청자</th> |
31 | 41 |
<th>기간</th> |
32 | 42 |
<th>신청일</th> |
33 |
- <th>상태</th> |
|
34 | 43 |
</tr> |
35 | 44 |
</thead> |
36 | 45 |
<!-- 동적으로 <td> 생성 --> |
37 | 46 |
<tbody> |
38 |
- <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> |
|
47 |
+ <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }" @click="goToPage(item)"> |
|
39 | 48 |
<td>{{ item.type }}</td> |
40 | 49 |
<td>{{ item.approvalType }}</td> |
41 | 50 |
<td>{{ item.applicant }}</td> |
42 | 51 |
<td>{{ item.period }}</td> |
43 | 52 |
<td>{{ item.requestDate }}</td> |
44 |
- <td :class="getStatusClass(item.status)">{{ item.status }}</td> |
|
45 | 53 |
</tr> |
46 | 54 |
</tbody> |
47 | 55 |
</table> |
... | ... | @@ -70,17 +78,29 @@ |
70 | 78 |
<h3><img :src="h3icon" alt="">승인 이력</h3> |
71 | 79 |
<div class="input-group"> |
72 | 80 |
<select name="" id="" class="form-select"> |
73 |
- <option value="">년도</option> |
|
81 |
+ <option :value="currentYear">{{ currentYear }}년</option> |
|
82 |
+ <option value="all">전체</option> |
|
83 |
+ <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> |
|
84 |
+ {{ year }}년 |
|
85 |
+ </option> |
|
74 | 86 |
</select> |
75 | 87 |
<select name="" id="" class="form-select"> |
76 |
- <option value="">월</option> |
|
88 |
+ <option :value="currentMonth">{{ currentMonth }}월</option> |
|
89 |
+ <option value="all">전체</option> |
|
90 |
+ <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> |
|
91 |
+ {{ month }}월 |
|
92 |
+ </option> |
|
77 | 93 |
</select> |
78 | 94 |
<select name="" id="" class="form-select"> |
79 |
- <option value="">상태</option> |
|
95 |
+ <option value="all">상태</option> |
|
96 |
+ <option value="">승인</option> |
|
97 |
+ <option value="">반려</option> |
|
80 | 98 |
</select> |
81 | 99 |
<div class="sch-input"> |
82 |
- <input type="text" class="form-control"> |
|
83 |
- <button class="ico-sch"><SearchOutlined /></button> |
|
100 |
+ <input type="text" class="form-control" placeholder="신청자명"> |
|
101 |
+ <button class="ico-sch"> |
|
102 |
+ <SearchOutlined /> |
|
103 |
+ </button> |
|
84 | 104 |
</div> |
85 | 105 |
</div> |
86 | 106 |
</div> |
... | ... | @@ -101,7 +121,7 @@ |
101 | 121 |
</thead> |
102 | 122 |
<!-- 동적으로 <td> 생성 --> |
103 | 123 |
<tbody> |
104 |
- <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> |
|
124 |
+ <tr v-for="(item, index) in filteredList" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> |
|
105 | 125 |
<td>{{ item.type }}</td> |
106 | 126 |
<td>{{ item.approvalType }}</td> |
107 | 127 |
<td>{{ item.applicant }}</td> |
... | ... | @@ -140,9 +160,18 @@ |
140 | 160 |
<script> |
141 | 161 |
import { ref } from 'vue'; |
142 | 162 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
163 |
+const currentYear = new Date().getFullYear(); |
|
164 |
+const currentMonth = new Date().getMonth() + 1; |
|
143 | 165 |
export default { |
144 | 166 |
data() { |
145 | 167 |
return { |
168 |
+ selectedStatus: 'all', |
|
169 |
+ currentMonth, |
|
170 |
+ selectedMonth: currentMonth, |
|
171 |
+ remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), |
|
172 |
+ currentYear, |
|
173 |
+ selectedYear: currentYear, |
|
174 |
+ remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), |
|
146 | 175 |
currentPage: 1, |
147 | 176 |
totalPages: 3, |
148 | 177 |
photoicon: "/client/resources/img/photo_icon.png", |
... | ... | @@ -153,6 +182,27 @@ |
153 | 182 |
selectedYear: '', |
154 | 183 |
selectedMonth: '', |
155 | 184 |
listData: [ |
185 |
+ { |
|
186 |
+ type: '연차', |
|
187 |
+ approvalType: '결재', |
|
188 |
+ applicant: '홍길동', |
|
189 |
+ period: '2025-05-10 ~ 2025-15-03', |
|
190 |
+ requestDate: '2025-04-25', |
|
191 |
+ }, { |
|
192 |
+ type: '출장-복명', |
|
193 |
+ approvalType: '전결', |
|
194 |
+ applicant: '홍길동', |
|
195 |
+ period: '2025-05-01 ~ 2025-05-03', |
|
196 |
+ requestDate: '2025-04-25', |
|
197 |
+ }, { |
|
198 |
+ type: '출장-품의', |
|
199 |
+ approvalType: '전결', |
|
200 |
+ applicant: '홍길동', |
|
201 |
+ period: '2025-05-01 ~ 2025-05-03', |
|
202 |
+ requestDate: '2025-04-25', |
|
203 |
+ } |
|
204 |
+ ], |
|
205 |
+ listData2: [ |
|
156 | 206 |
{ |
157 | 207 |
type: '연차', |
158 | 208 |
approvalType: '결재', |
... | ... | @@ -172,11 +222,29 @@ |
172 | 222 |
}; |
173 | 223 |
}, |
174 | 224 |
computed: { |
225 |
+ filteredList() { |
|
226 |
+ if (this.selectedStatus === 'all') { |
|
227 |
+ return this.listData2; |
|
228 |
+ } |
|
229 |
+ return this.listData2.filter((item) => item.status === this.selectedStatus); |
|
230 |
+ }, |
|
175 | 231 |
}, |
176 |
- components:{ |
|
232 |
+ components: { |
|
177 | 233 |
SearchOutlined |
178 | 234 |
}, |
179 | 235 |
methods: { |
236 |
+ goToPage(item) { |
|
237 |
+ const type = item.type; |
|
238 |
+ if (type === '연차' || type === '반차') { |
|
239 |
+ this.$router.push('/hyugaDetail.page'); |
|
240 |
+ } else if (type === '출장-품의') { |
|
241 |
+ this.$router.push('/ChuljangPumui.page'); |
|
242 |
+ } else if (type === '출장-복명') { |
|
243 |
+ this.$router.push('/ChuljangBokmyeong.page'); |
|
244 |
+ } else { |
|
245 |
+ alert('이동할 수 없는 항목입니다.'); |
|
246 |
+ } |
|
247 |
+ }, |
|
180 | 248 |
changePage(page) { |
181 | 249 |
if (page < 1 || page > this.totalPages) return; |
182 | 250 |
this.currentPage = page; |
... | ... | @@ -230,4 +298,6 @@ |
230 | 298 |
}; |
231 | 299 |
</script> |
232 | 300 |
|
233 |
-<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
|
301 |
+<style scoped> |
|
302 |
+tr{cursor: pointer;} |
|
303 |
+</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/Manager/approval/approvalRequest.vue
+++ client/views/pages/Manager/approval/approvalRequest.vue
... | ... | @@ -1,108 +1,137 @@ |
1 | 1 |
<template> |
2 |
-<div class="col-lg-12"> |
|
3 |
- <div class="card"> |
|
4 |
- <div class="card-body"> |
|
5 |
- <h2 class="card-title">결재 요청</h2> |
|
6 |
- <div class="sch-form-wrap"> |
|
7 |
- <div class="input-group"> |
|
8 |
- <select name="" id="" class="form-select"> |
|
9 |
- <option value="">년도</option> |
|
10 |
- </select> |
|
11 |
- <select name="" id="" class="form-select"> |
|
12 |
- <option value="">월</option> |
|
13 |
- </select> |
|
14 |
- <select name="" id="" class="form-select"> |
|
15 |
- <option value="">구분</option> |
|
16 |
- </select> |
|
2 |
+ <div class="col-lg-12"> |
|
3 |
+ <div class="card"> |
|
4 |
+ <div class="card-body"> |
|
5 |
+ <h2 class="card-title">결재 요청</h2> |
|
6 |
+ <div class="sch-form-wrap"> |
|
7 |
+ <div class="input-group"> |
|
8 |
+ <select name="" id="" class="form-select"> |
|
9 |
+ <option :value="currentYear">{{ currentYear }}년</option> |
|
10 |
+ <option value="all">전체</option> |
|
11 |
+ <option |
|
12 |
+ v-for="year in remainingYears" |
|
13 |
+ :key="year" |
|
14 |
+ :value="year" |
|
15 |
+ v-if="year !== currentYear" |
|
16 |
+ > |
|
17 |
+ {{ year }}년 |
|
18 |
+ </option> |
|
19 |
+ </select> |
|
20 |
+ <select name="" id="" class="form-select"> |
|
21 |
+ <option :value="currentMonth">{{ currentMonth }}월</option> |
|
22 |
+ <option value="all">전체</option> |
|
23 |
+ <option |
|
24 |
+ v-for="month in remainingMonths" |
|
25 |
+ :key="month" |
|
26 |
+ :value="month" |
|
27 |
+ v-if="month !== currentMonth" |
|
28 |
+ > |
|
29 |
+ {{ month }}월 |
|
30 |
+ </option> |
|
31 |
+ </select> |
|
32 |
+ <select name="" id="" class="form-select"> |
|
33 |
+ <option value="" selected>전체</option> |
|
34 |
+ <option value="">출장-복명</option> |
|
35 |
+ <option value="">출장-품의</option> |
|
36 |
+ <option value="">연차</option> |
|
37 |
+ <option value="">반차-오전</option> |
|
38 |
+ <option value="">반차-오후</option> |
|
39 |
+ <option value="">대체휴가</option> |
|
40 |
+ <option value="">공가</option> |
|
41 |
+ <option value="">병가</option> |
|
42 |
+ </select> |
|
43 |
+ </div> |
|
44 |
+ </div> |
|
45 |
+ |
|
46 |
+ <!-- Table --> |
|
47 |
+ <div class="tbl-wrap"> |
|
48 |
+ <table id="myTable" class="tbl data"> |
|
49 |
+ <!-- 동적으로 <th> 생성 --> |
|
50 |
+ <thead> |
|
51 |
+ <tr> |
|
52 |
+ <th>구분 </th> |
|
53 |
+ <th>결재구분</th> |
|
54 |
+ <th>신청자</th> |
|
55 |
+ <th>기간</th> |
|
56 |
+ <th>신청일</th> |
|
57 |
+ <th>상태</th> |
|
58 |
+ </tr> |
|
59 |
+ </thead> |
|
60 |
+ <!-- 동적으로 <td> 생성 --> |
|
61 |
+ <tbody> |
|
62 |
+ <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> |
|
63 |
+ <td>{{ item.type }}</td> |
|
64 |
+ <td>{{ item.approvalType }}</td> |
|
65 |
+ <td>{{ item.applicant }}</td> |
|
66 |
+ <td>{{ item.period }}</td> |
|
67 |
+ <td>{{ item.requestDate }}</td> |
|
68 |
+ <td :class="getStatusClass(item.status)">{{ item.status }}</td> |
|
69 |
+ </tr> |
|
70 |
+ </tbody> |
|
71 |
+ </table> |
|
72 |
+ |
|
73 |
+ </div> |
|
74 |
+ <div class="pagination"> |
|
75 |
+ <ul> |
|
76 |
+ <!-- 왼쪽 화살표 (이전 페이지) --> |
|
77 |
+ <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> |
|
78 |
+ < |
|
79 |
+ </li> |
|
80 |
+ |
|
81 |
+ <!-- 페이지 번호 --> |
|
82 |
+ <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" |
|
83 |
+ @click="changePage(page)"> |
|
84 |
+ {{ page }} |
|
85 |
+ </li> |
|
86 |
+ |
|
87 |
+ <!-- 오른쪽 화살표 (다음 페이지) --> |
|
88 |
+ <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> |
|
89 |
+ > |
|
90 |
+ </li> |
|
91 |
+ </ul> |
|
92 |
+ </div> |
|
93 |
+ <!-- End Table --> |
|
94 |
+ <div class="buttons"> |
|
95 |
+ <button type="button" class="btn sm primary" @click="showOptions = true"> |
|
96 |
+ 등록 |
|
97 |
+ </button> |
|
98 |
+ |
|
99 |
+ <!-- 신청 종류 선택 모달 --> |
|
100 |
+ <div v-if="showOptions" class="popup-overlay"> |
|
101 |
+ <div class="popup-content"> |
|
102 |
+ <div class="card"> |
|
103 |
+ <div class="card-body"> |
|
104 |
+ <h2 class="card-title">신청종류선택</h2> |
|
105 |
+ <div class="buttons"> |
|
106 |
+ <button class="btn hyuga" @click="goToPage('휴가')">휴가신청</button> |
|
107 |
+ <button class="btn chuljang" @click="goToPage('출장')">출장신청</button> |
|
108 |
+ </div> |
|
109 |
+ </div> |
|
110 |
+ </div> |
|
111 |
+ <button class="close-btn" @click="showOptions = false"> <CloseCircleFilled /></button> |
|
112 |
+ </div> |
|
113 |
+ |
|
114 |
+ </div> |
|
17 | 115 |
</div> |
18 | 116 |
</div> |
19 |
- |
|
20 |
- <!-- Table --> |
|
21 |
- <div class="tbl-wrap"> |
|
22 |
- <table id="myTable" class="tbl data"> |
|
23 |
- <!-- 동적으로 <th> 생성 --> |
|
24 |
- <thead> |
|
25 |
- <tr> |
|
26 |
- <th>구분 </th> |
|
27 |
- <th>결재구분</th> |
|
28 |
- <th>신청자</th> |
|
29 |
- <th>기간</th> |
|
30 |
- <th>신청일</th> |
|
31 |
- <th>상태</th> |
|
32 |
- </tr> |
|
33 |
- </thead> |
|
34 |
- <!-- 동적으로 <td> 생성 --> |
|
35 |
- <tbody> |
|
36 |
- <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }"> |
|
37 |
- <td>{{ item.type }}</td> |
|
38 |
- <td>{{ item.approvalType }}</td> |
|
39 |
- <td>{{ item.applicant }}</td> |
|
40 |
- <td>{{ item.period }}</td> |
|
41 |
- <td>{{ item.requestDate }}</td> |
|
42 |
- <td :class="getStatusClass(item.status)">{{ item.status }}</td> |
|
43 |
- </tr> |
|
44 |
- </tbody> |
|
45 |
- </table> |
|
46 |
- |
|
47 |
- </div> |
|
48 |
- <div class="pagination"> |
|
49 |
- <ul> |
|
50 |
- <!-- 왼쪽 화살표 (이전 페이지) --> |
|
51 |
- <li |
|
52 |
- class="arrow" |
|
53 |
- :class="{ disabled: currentPage === 1 }" |
|
54 |
- @click="changePage(currentPage - 1)" |
|
55 |
- > |
|
56 |
- < |
|
57 |
- </li> |
|
58 |
- |
|
59 |
- <!-- 페이지 번호 --> |
|
60 |
- <li |
|
61 |
- v-for="page in totalPages" |
|
62 |
- :key="page" |
|
63 |
- :class="{ active: currentPage === page }" |
|
64 |
- @click="changePage(page)" |
|
65 |
- > |
|
66 |
- {{ page }} |
|
67 |
- </li> |
|
68 |
- |
|
69 |
- <!-- 오른쪽 화살표 (다음 페이지) --> |
|
70 |
- <li |
|
71 |
- class="arrow" |
|
72 |
- :class="{ disabled: currentPage === totalPages }" |
|
73 |
- @click="changePage(currentPage + 1)" |
|
74 |
- > |
|
75 |
- > |
|
76 |
- </li> |
|
77 |
- </ul> |
|
78 |
- </div> |
|
79 |
- <!-- End Table --> |
|
80 |
- <div class="buttons"> |
|
81 |
- <button type="button" class="btn sm primary" @click="showOptions = true"> |
|
82 |
- 등록 |
|
83 |
- </button> |
|
84 |
- |
|
85 |
- <!-- 신청 종류 선택 모달 --> |
|
86 |
- <div v-if="showOptions" class="modal-overlay"> |
|
87 |
- <div class="modal-box"> |
|
88 |
- <p>신청 종류를 선택하세요</p> |
|
89 |
- <button @click="goToPage('휴가')">휴가신청</button> |
|
90 |
- <button @click="goToPage('출장')">출장신청</button> |
|
91 |
- <button class="cancel" @click="showOptions = false">취소</button> |
|
92 |
- </div> |
|
93 | 117 |
</div> |
94 | 118 |
</div> |
95 |
- </div> |
|
96 |
- </div> |
|
97 |
-</div> |
|
98 | 119 |
</template> |
99 | 120 |
|
100 | 121 |
<script> |
101 | 122 |
import { ref } from 'vue'; |
102 |
- |
|
123 |
+import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; |
|
124 |
+const currentYear = new Date().getFullYear(); |
|
125 |
+const currentMonth = new Date().getMonth() + 1; |
|
103 | 126 |
export default { |
104 | 127 |
data() { |
105 | 128 |
return { |
129 |
+ currentMonth, |
|
130 |
+ selectedMonth: currentMonth, |
|
131 |
+ remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), |
|
132 |
+ currentYear, |
|
133 |
+ selectedYear: currentYear, |
|
134 |
+ remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), |
|
106 | 135 |
showOptions: false, |
107 | 136 |
currentPage: 1, |
108 | 137 |
totalPages: 3, |
... | ... | @@ -113,23 +142,26 @@ |
113 | 142 |
selectedYear: '', |
114 | 143 |
selectedMonth: '', |
115 | 144 |
listData: [ |
116 |
- { |
|
117 |
- type: '연차', |
|
118 |
- approvalType: '결재', |
|
119 |
- applicant: '홍길동', |
|
120 |
- period: '2025-05-10 ~ 2025-15-03', |
|
121 |
- requestDate: '2025-04-25', |
|
122 |
- status: '대기' |
|
123 |
- }, { |
|
124 |
- type: '반차', |
|
125 |
- approvalType: '전결', |
|
126 |
- applicant: '홍길동', |
|
127 |
- period: '2025-05-01 ~ 2025-05-03', |
|
128 |
- requestDate: '2025-04-25', |
|
129 |
- status: '승인' |
|
130 |
- }], |
|
145 |
+ { |
|
146 |
+ type: '연차', |
|
147 |
+ approvalType: '결재', |
|
148 |
+ applicant: '홍길동', |
|
149 |
+ period: '2025-05-10 ~ 2025-15-03', |
|
150 |
+ requestDate: '2025-04-25', |
|
151 |
+ status: '대기' |
|
152 |
+ }, { |
|
153 |
+ type: '반차', |
|
154 |
+ approvalType: '전결', |
|
155 |
+ applicant: '홍길동', |
|
156 |
+ period: '2025-05-01 ~ 2025-05-03', |
|
157 |
+ requestDate: '2025-04-25', |
|
158 |
+ status: '승인' |
|
159 |
+ }], |
|
131 | 160 |
filteredData: [], |
132 | 161 |
}; |
162 |
+ }, |
|
163 |
+ components: { |
|
164 |
+ SearchOutlined, CloseCircleFilled |
|
133 | 165 |
}, |
134 | 166 |
computed: { |
135 | 167 |
}, |
... | ... | @@ -151,28 +183,28 @@ |
151 | 183 |
} |
152 | 184 |
}, |
153 | 185 |
goToPage(type) { |
154 |
- if (type === '휴가') { |
|
155 |
- this.$router.push('/HyugaInsert.page'); |
|
156 |
- } else if (type === '출장') { |
|
157 |
- this.$router.push('/ChuljangDetail.page'); |
|
158 |
- } |
|
159 |
-}, |
|
186 |
+ if (type === '휴가') { |
|
187 |
+ this.$router.push('/HyugaInsert.page'); |
|
188 |
+ } else if (type === '출장') { |
|
189 |
+ this.$router.push('/ChuljangDetail.page'); |
|
190 |
+ } |
|
191 |
+ }, |
|
160 | 192 |
getStatusClass(status) { |
161 | 193 |
if (status === '승인') return 'status-approved'; |
162 | 194 |
if (status === '대기') return 'status-pending'; |
163 | 195 |
return ''; |
164 | 196 |
}, |
165 | 197 |
isPastPeriod(period) { |
166 |
- // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 |
|
167 |
- const endDateStr = period.split('~')[1]?.trim(); |
|
168 |
- if (!endDateStr) return false; |
|
198 |
+ // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 |
|
199 |
+ const endDateStr = period.split('~')[1]?.trim(); |
|
200 |
+ if (!endDateStr) return false; |
|
169 | 201 |
|
170 |
- const endDate = new Date(endDateStr); |
|
171 |
- const today = new Date(); |
|
202 |
+ const endDate = new Date(endDateStr); |
|
203 |
+ const today = new Date(); |
|
172 | 204 |
|
173 |
- // 현재 날짜보다 과거면 true |
|
174 |
- return endDate < today; |
|
175 |
- } |
|
205 |
+ // 현재 날짜보다 과거면 true |
|
206 |
+ return endDate < today; |
|
207 |
+ } |
|
176 | 208 |
}, |
177 | 209 |
created() { |
178 | 210 |
}, |
+++ client/views/pages/Manager/asset/CarInfoManagement.vue
... | ... | @@ -0,0 +1,275 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">차량정보 관리</h2> | |
5 | + <div class="flex align-top"> | |
6 | + <div class="sch-form-wrap search"> | |
7 | + <div class="input-group" style="display: flex;"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option value="">전체</option> | |
10 | + <option value="">차량명</option> | |
11 | + <option value="">차량번호</option> | |
12 | + </select> | |
13 | + <div class="sch-input"> | |
14 | + <input type="text" class="form-control"> | |
15 | + <button class="ico-sch"> | |
16 | + <SearchOutlined /> | |
17 | + </button> | |
18 | + </div> | |
19 | + </div> | |
20 | + <div class="tbl-wrap table-scroll"> | |
21 | + <table id="myTable" class="tbl data"> | |
22 | + <!-- 동적으로 <th> 생성 --> | |
23 | + <thead> | |
24 | + <tr> | |
25 | + <th>차량목록 </th> | |
26 | + </tr> | |
27 | + </thead> | |
28 | + <!-- 동적으로 <td> 생성 --> | |
29 | + <tbody> | |
30 | + <tr v-for="(item, index) in listData" :key="index"> | |
31 | + <td></td> | |
32 | + </tr> | |
33 | + </tbody> | |
34 | + </table> | |
35 | + | |
36 | + </div> | |
37 | + </div> | |
38 | + | |
39 | + <div style="width: 100%;"> | |
40 | + <div class=" sch-form-wrap title-wrap"> | |
41 | + <h3><img :src="h3icon" alt="">차량 정보</h3> | |
42 | + <div class="buttons" style="margin: 0;"> | |
43 | + <button type="submit" class="btn sm tertiary">신규</button> | |
44 | + <button type="reset" class="btn sm secondary">등록</button> | |
45 | + <button type="delete" class="btn sm btn-red">삭제</button> | |
46 | + </div> | |
47 | + </div> | |
48 | + <form class="row g-3 pt-3 needs-validation " @submit.prevent="handleSubmit" | |
49 | + style="margin-bottom: 3rem;"> | |
50 | + <div class="col-12"> | |
51 | + <div class="col-12 border-x"> | |
52 | + <label for="where" class="form-label"><p>차종<p class="require"><img :src="require" alt=""></p></p></label> | |
53 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
54 | + </div> | |
55 | + <div class="col-12 border-x"> | |
56 | + <label for="where" class="form-label"><p>차량번호<p class="require"><img :src="require" alt=""></p></p></label> | |
57 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
58 | + </div> | |
59 | + | |
60 | + </div> | |
61 | + <div class="col-12"> | |
62 | + <div class="col-12 border-x"> | |
63 | + <label for="where" class="form-label">연료종류</label> | |
64 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
65 | + </div> | |
66 | + <div class="col-12 border-x"> | |
67 | + <label for="where" class="form-label"><p>소유형태<p class="require"><img :src="require" alt=""></p></p></label> | |
68 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
69 | + </div> | |
70 | + | |
71 | + </div> | |
72 | + <div class="col-12 "> | |
73 | + <label for="prvonsh" class="form-label"><p>차량명<p class="require"><img :src="require" alt=""></p></p></label> | |
74 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
75 | + </div> | |
76 | + <div class="col-12 "> | |
77 | + <label for="prvonsh" class="form-label"><p>담당자<p class="require"><img :src="require" alt=""></p></p></label> | |
78 | + <input type="text" class="form-control textarea" id="reason" v-model="selectedname" readonly/> | |
79 | + <input type="button" class="form-control " value="검색" @click="showPopup = true" /> | |
80 | + <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval"/> | |
81 | + </div> | |
82 | + <div class="col-12 chuljang "> | |
83 | + <label for="prvonsh" class="form-label">비고</label> | |
84 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
85 | + </div> | |
86 | + <div class="col-12 border-x input-radio"> | |
87 | + <label for="prvonsh" class="form-label"> <p>상태 | |
88 | + <p class="require"><img :src="require" alt=""></p> | |
89 | + </p></label> | |
90 | + <select class="form-select" > | |
91 | + <option value="선택">선택</option> | |
92 | + <option value=""></option> | |
93 | + <option value=""></option> | |
94 | + </select> | |
95 | + </div> | |
96 | + | |
97 | + | |
98 | + </form> | |
99 | + <div class=" sch-form-wrap title-wrap"> | |
100 | + <h3><img :src="h3icon" alt="">예약현황</h3> | |
101 | + </div> | |
102 | + <div class="tbl-wrap chk-area"> | |
103 | + <table id="myTable" class="tbl data"> | |
104 | + | |
105 | + <thead> | |
106 | + <tr> | |
107 | + <th>차종</th> | |
108 | + <th>운행자</th> | |
109 | + <th>기간</th> | |
110 | + <th>상태</th> | |
111 | + </tr> | |
112 | + </thead> | |
113 | + <!-- 동적으로 <td> 생성 --> | |
114 | + <tbody> | |
115 | + <tr v-for="(item, index) in listData" :key="index"> | |
116 | + <td>{{ item.type }}</td> | |
117 | + <td>{{ item.driver }}</td> | |
118 | + <td>{{ item.startDate }} ~ {{ item.endDate }}</td> | |
119 | + <td :class="getStatusClass(item.status)"> | |
120 | + {{ item.status }} | |
121 | + </td> | |
122 | + </tr> | |
123 | + </tbody> | |
124 | + </table> | |
125 | + | |
126 | + </div> | |
127 | + <div class="pagination"> | |
128 | + <ul> | |
129 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
130 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
131 | + < | |
132 | + </li> | |
133 | + | |
134 | + <!-- 페이지 번호 --> | |
135 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
136 | + @click="changePage(page)"> | |
137 | + {{ page }} | |
138 | + </li> | |
139 | + | |
140 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
141 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
142 | + > | |
143 | + </li> | |
144 | + </ul> | |
145 | + </div> | |
146 | + </div> | |
147 | + </div> | |
148 | + </div> | |
149 | + | |
150 | + </div> | |
151 | + | |
152 | +</template> | |
153 | + | |
154 | +<script> | |
155 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
156 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
157 | +import HrPopup from "../../../component/Popup/HrPopup.vue"; | |
158 | +export default { | |
159 | + data() { | |
160 | + return { | |
161 | + showPopup: false, | |
162 | + selectedname: '', | |
163 | + approvals: [], | |
164 | + require: "/client/resources/img/require.png", | |
165 | + h3icon: "/client/resources/img/h3icon.png", | |
166 | + photoicon: "/client/resources/img/photo_icon.png", | |
167 | + img1: "/client/resources/img/img.png", | |
168 | + icon1: "/client/resources/img/icon.png", | |
169 | + dateicon: "/client/resources/img/date.png", | |
170 | + startbtn: "/client/resources/img/start.png", | |
171 | + stopbtn: "/client/resources/img/stop.png", | |
172 | + moreicon: "/client/resources/img/more.png", | |
173 | + today: new Date().toLocaleDateString('ko-KR', { | |
174 | + year: 'numeric', | |
175 | + month: '2-digit', | |
176 | + day: '2-digit', | |
177 | + weekday: 'short', | |
178 | + }), | |
179 | + time: this.getCurrentTime(), | |
180 | + listData: [ | |
181 | + { | |
182 | + type: 'SUV', | |
183 | + driver: '김철수', | |
184 | + startDate: '2025-05-01', | |
185 | + endDate: '2025-05-03', | |
186 | + status: '예약', // 사용중 | |
187 | + }, | |
188 | + { | |
189 | + type: '세단', | |
190 | + driver: '이영희', | |
191 | + startDate: '2025-05-05', | |
192 | + endDate: '2025-05-06', | |
193 | + status: '예약', // 예약됨 | |
194 | + }, | |
195 | + { | |
196 | + type: '트럭', | |
197 | + driver: '박민호', | |
198 | + startDate: '2025-04-25', | |
199 | + endDate: '2025-04-28', | |
200 | + status: '반납', // 반납됨 | |
201 | + }, | |
202 | + ], | |
203 | + } | |
204 | + }, | |
205 | + components: { | |
206 | + SearchOutlined, HrPopup | |
207 | + }, | |
208 | + methods: { | |
209 | + addApproval(selectedUser) { | |
210 | + this.approvals.push({ | |
211 | + name: selectedUser.name | |
212 | + }); | |
213 | + | |
214 | + this.selectedname = selectedUser.name; // 입력창에 표시 | |
215 | + this.showPopup = false; | |
216 | + }, | |
217 | + formatBudget(amount) { | |
218 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
219 | + }, | |
220 | + isPastPeriod(period) { | |
221 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
222 | + const endDateStr = period.split('~')[1]?.trim(); | |
223 | + if (!endDateStr) return false; | |
224 | + | |
225 | + const endDate = new Date(endDateStr); | |
226 | + const today = new Date(); | |
227 | + | |
228 | + // 현재 날짜보다 과거면 true | |
229 | + return endDate < today; | |
230 | + }, | |
231 | + getStatusClass(status) { | |
232 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
233 | + }, | |
234 | + getStatusClass(status) { | |
235 | + if (status === '예약') return 'status-pending'; | |
236 | + if (status === '반납') return 'status-approved'; | |
237 | + | |
238 | + // Default empty string | |
239 | + return ''; | |
240 | + }, | |
241 | + getCurrentTime() { | |
242 | + const now = new Date(); | |
243 | + const hours = String(now.getHours()).padStart(2, '0'); | |
244 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
245 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
246 | + return `${hours}:${minutes}:${seconds}`; | |
247 | + }, | |
248 | + getCategoryClass(category) { | |
249 | + switch (category) { | |
250 | + case '용역': return 'category-service'; | |
251 | + case '내부': return 'category-internal'; | |
252 | + case '국가과제': return 'category-government'; | |
253 | + default: return ''; | |
254 | + } | |
255 | + }, | |
256 | + }, | |
257 | + watch: { | |
258 | + | |
259 | + }, | |
260 | + computed: { | |
261 | + | |
262 | + }, | |
263 | + mounted() { | |
264 | + console.log('main mounted'); | |
265 | + setInterval(() => { | |
266 | + this.time = this.getCurrentTime(); | |
267 | + }, 1000); | |
268 | + } | |
269 | +} | |
270 | +</script> | |
271 | +<style scoped> | |
272 | +tr { | |
273 | + cursor: pointer; | |
274 | +} | |
275 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/asset/CarList.vue
... | ... | @@ -0,0 +1,223 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">사용현황</h2> | |
6 | + <div class="sch-form-wrap title-wrap"> | |
7 | + <h3><img :src="h3icon" alt="">예약현황</h3> | |
8 | + <div class="input-group"> | |
9 | + <select name="" id="" class="form-select"> | |
10 | + <option value="">전체</option> | |
11 | + <option value="">차량명</option> | |
12 | + <option value="">차량번호</option> | |
13 | + <option value="">운행자</option> | |
14 | + </select> | |
15 | + <div class="sch-input"> | |
16 | + <input type="text" class="form-control"> | |
17 | + <button class="ico-sch"><SearchOutlined /></button> | |
18 | + </div> | |
19 | + </div> | |
20 | + </div> | |
21 | + | |
22 | + <!-- Table --> | |
23 | + <div class="tbl-wrap"> | |
24 | + <table id="myTable" class="tbl data"> | |
25 | + <!-- 동적으로 <th> 생성 --> | |
26 | + <thead> | |
27 | + <tr> | |
28 | + <th>차종 </th> | |
29 | + <th>차량번호</th> | |
30 | + <th>차량명</th> | |
31 | + <th>부서</th> | |
32 | + <th>운행자</th> | |
33 | + <th>기간</th> | |
34 | + </tr> | |
35 | + </thead> | |
36 | + <!-- 동적으로 <td> 생성 --> | |
37 | + <tbody> | |
38 | + <tr v-for="(item, index) in listData" :key="index"> | |
39 | + <td>{{ item.type }}</td> | |
40 | + <td>{{ item.number }}</td> | |
41 | + <td>{{ item.name }}</td> | |
42 | + <td>{{ item.department }}</td> | |
43 | + <td>{{ item.driver }}</td> | |
44 | + <td>{{ item.startDate }} ~ {{ item.endDate }}</td> | |
45 | + </tr> | |
46 | + </tbody> | |
47 | + </table> | |
48 | + | |
49 | + </div> | |
50 | + <div class="pagination"> | |
51 | + <ul> | |
52 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
53 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
54 | + < | |
55 | + </li> | |
56 | + | |
57 | + <!-- 페이지 번호 --> | |
58 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
59 | + @click="changePage(page)"> | |
60 | + {{ page }} | |
61 | + </li> | |
62 | + | |
63 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
64 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
65 | + > | |
66 | + </li> | |
67 | + </ul> | |
68 | + </div> | |
69 | + <div class="sch-form-wrap title-wrap"> | |
70 | + <h3><img :src="h3icon" alt="">사용이력</h3> | |
71 | + <div class="input-group"> | |
72 | + <select name="" id="" class="form-select"> | |
73 | + <option value="">전체</option> | |
74 | + <option value="">차량명</option> | |
75 | + <option value="">차량번호</option> | |
76 | + <option value="">운행자</option> | |
77 | + </select> | |
78 | + <div class="sch-input"> | |
79 | + <input type="text" class="form-control"> | |
80 | + <button class="ico-sch"><SearchOutlined /></button> | |
81 | + </div> | |
82 | + </div> | |
83 | + </div> | |
84 | + | |
85 | + <!-- Table --> | |
86 | + <div class="tbl-wrap"> | |
87 | + <table id="myTable" class="tbl data"> | |
88 | + <!-- 동적으로 <th> 생성 --> | |
89 | + <thead> | |
90 | + <tr> | |
91 | + <th>차종 </th> | |
92 | + <th>차량번호</th> | |
93 | + <th>차량명</th> | |
94 | + <th>부서</th> | |
95 | + <th>운행자</th> | |
96 | + <th>기간</th> | |
97 | + </tr> | |
98 | + </thead> | |
99 | + <!-- 동적으로 <td> 생성 --> | |
100 | + <tbody> | |
101 | + <tr v-for="(item, index) in listData" :key="index"> | |
102 | + <td>{{ item.type }}</td> | |
103 | + <td>{{ item.number }}</td> | |
104 | + <td>{{ item.name }}</td> | |
105 | + <td>{{ item.department }}</td> | |
106 | + <td>{{ item.driver }}</td> | |
107 | + <td>{{ item.startDate }} ~ {{ item.endDate }}</td> | |
108 | + </tr> | |
109 | + </tbody> | |
110 | + </table> | |
111 | + | |
112 | + </div> | |
113 | + <div class="pagination"> | |
114 | + <ul> | |
115 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
116 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
117 | + < | |
118 | + </li> | |
119 | + | |
120 | + <!-- 페이지 번호 --> | |
121 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
122 | + @click="changePage(page)"> | |
123 | + {{ page }} | |
124 | + </li> | |
125 | + | |
126 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
127 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
128 | + > | |
129 | + </li> | |
130 | + </ul> | |
131 | + </div> | |
132 | + </div> | |
133 | + </div> | |
134 | + </div> | |
135 | +</template> | |
136 | + | |
137 | +<script> | |
138 | +import { ref } from 'vue'; | |
139 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
140 | +export default { | |
141 | + data() { | |
142 | + return { | |
143 | + currentPage: 1, | |
144 | + totalPages: 3, | |
145 | + photoicon: "/client/resources/img/photo_icon.png", | |
146 | + h3icon: "/client/resources/img/h3icon.png", | |
147 | + // 데이터 초기화 | |
148 | + years: [2023, 2024, 2025], // 연도 목록 | |
149 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
150 | + selectedYear: '', | |
151 | + selectedMonth: '', | |
152 | + listData: [ | |
153 | + { | |
154 | + type: 'SUV', | |
155 | + number: '12가3456', | |
156 | + name: '쏘렌토', | |
157 | + department: '총무부', | |
158 | + driver: '김철수', | |
159 | + startDate: '2025-05-01', | |
160 | + endDate: '2025-05-10', | |
161 | + }, | |
162 | + { | |
163 | + type: '세단', | |
164 | + number: '34나7890', | |
165 | + name: '그랜저', | |
166 | + department: '영업부', | |
167 | + driver: '이영희', | |
168 | + startDate: '2025-05-03', | |
169 | + endDate: '2025-05-04', | |
170 | + },], | |
171 | + filteredData: [], | |
172 | + }; | |
173 | + }, | |
174 | + computed: { | |
175 | + }, | |
176 | + components:{ | |
177 | + SearchOutlined | |
178 | + }, | |
179 | + methods: { | |
180 | + changePage(page) { | |
181 | + if (page < 1 || page > this.totalPages) return; | |
182 | + this.currentPage = page; | |
183 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
184 | + }, | |
185 | + async onClickSubmit() { | |
186 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
187 | + const { mutate, onDone, onError } = useMutation(mygql); | |
188 | + | |
189 | + try { | |
190 | + const result = await mutate(); | |
191 | + console.log(result); | |
192 | + } catch (error) { | |
193 | + console.error('Mutation error:', error); | |
194 | + } | |
195 | + }, | |
196 | + registerLeave() { | |
197 | + console.log("등록 버튼 클릭됨"); | |
198 | + | |
199 | + // Vue의 반응성 문제를 피하기 위해, 새로운 객체를 추가합니다. | |
200 | + this.DeptData = [ | |
201 | + ...this.DeptData, | |
202 | + { member: '', deptNM: '', acceptTerms: false } | |
203 | + ]; | |
204 | + | |
205 | + console.log(this.DeptData); // 배열 상태 출력 | |
206 | + }, | |
207 | + getStatusClass(status) { | |
208 | + if (status === '승인') return 'status-approved'; | |
209 | + if (status === '대기') return 'status-pending'; | |
210 | + return ''; | |
211 | + }, | |
212 | + | |
213 | + }, | |
214 | + created() { | |
215 | + }, | |
216 | + mounted() { | |
217 | + | |
218 | + | |
219 | + }, | |
220 | +}; | |
221 | +</script> | |
222 | + | |
223 | +<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/asset/CardInfoManagement.vue
... | ... | @@ -0,0 +1,268 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">카드정보 관리</h2> | |
5 | + <div class="flex align-top"> | |
6 | + <div class="sch-form-wrap search"> | |
7 | + <div class="input-group" style="display: flex;"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option value="">전체</option> | |
10 | + </select> | |
11 | + <div class="sch-input"> | |
12 | + <input type="text" class="form-control"> | |
13 | + <button class="ico-sch"> | |
14 | + <SearchOutlined /> | |
15 | + </button> | |
16 | + </div> | |
17 | + </div> | |
18 | + <div class="tbl-wrap table-scroll"> | |
19 | + <table id="myTable" class="tbl data"> | |
20 | + <!-- 동적으로 <th> 생성 --> | |
21 | + <thead> | |
22 | + <tr> | |
23 | + <th>카드목록 </th> | |
24 | + </tr> | |
25 | + </thead> | |
26 | + <!-- 동적으로 <td> 생성 --> | |
27 | + <tbody> | |
28 | + <tr v-for="(item, index) in listData" :key="index"> | |
29 | + <td></td> | |
30 | + </tr> | |
31 | + </tbody> | |
32 | + </table> | |
33 | + | |
34 | + </div> | |
35 | + </div> | |
36 | + | |
37 | + <div style="width: 100%;"> | |
38 | + <div class=" sch-form-wrap title-wrap"> | |
39 | + <h3><img :src="h3icon" alt="">카드 정보</h3> | |
40 | + <div class="buttons" style="margin: 0;"> | |
41 | + <button type="submit" class="btn sm tertiary">신규</button> | |
42 | + <button type="reset" class="btn sm secondary">등록</button> | |
43 | + <button type="delete" class="btn sm btn-red">삭제</button> | |
44 | + </div> | |
45 | + </div> | |
46 | + <form class="row g-3 pt-3 needs-validation " @submit.prevent="handleSubmit" style="margin-bottom: 3rem;"> | |
47 | + | |
48 | + <div class="col-12 "> | |
49 | + <label for="prvonsh" class="form-label"> | |
50 | + <p>카드명 | |
51 | + <p class="require"><img :src="require" alt=""></p> | |
52 | + </p> | |
53 | + </label> | |
54 | + <input type="text" class="form-control " id="reason" v-model="reason" /> | |
55 | + </div> | |
56 | + <div class="col-12 "> | |
57 | + <label for="prvonsh" class="form-label"> | |
58 | + <p>담당자 | |
59 | + <p class="require"><img :src="require" alt=""></p> | |
60 | + </p> | |
61 | + </label> | |
62 | + <input type="text" class="form-control " id="reason" v-model="selectedname" readonly /> | |
63 | + <input type="button" class="form-control " value="검색" @click="showPopup = true" /> | |
64 | + <!-- 팝업 --> | |
65 | + <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval"/> | |
66 | + </div> | |
67 | + <div class="col-12 chuljang "> | |
68 | + <label for="prvonsh" class="form-label">비고</label> | |
69 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
70 | + </div> | |
71 | + <div class="col-12 border-x input-radio"> | |
72 | + <label for="prvonsh" class="form-label"> | |
73 | + <p>사용여부 | |
74 | + <p class="require"><img :src="require" alt=""></p> | |
75 | + </p> | |
76 | + </label> | |
77 | + <div class="chk-area"> | |
78 | + <div class="form-check"> | |
79 | + <input type="radio" name="rdo_1" id="rdo_1"> | |
80 | + <label for="rdo_1">사용</label> | |
81 | + </div> | |
82 | + <div class="form-check"> | |
83 | + <input type="radio" name="rdo_1" id="rdo_2" checked> | |
84 | + <label for="rdo_2">미사용</label> | |
85 | + </div> | |
86 | + </div> | |
87 | + </div> | |
88 | + | |
89 | + | |
90 | + </form> | |
91 | + <div class=" sch-form-wrap title-wrap"> | |
92 | + <h3><img :src="h3icon" alt="">예약현황</h3> | |
93 | + </div> | |
94 | + <div class="tbl-wrap chk-area"> | |
95 | + <table id="myTable" class="tbl data"> | |
96 | + | |
97 | + <thead> | |
98 | + <tr> | |
99 | + <th>부서</th> | |
100 | + <th>신청자</th> | |
101 | + <th>기간</th> | |
102 | + <th>상태</th> | |
103 | + </tr> | |
104 | + </thead> | |
105 | + <!-- 동적으로 <td> 생성 --> | |
106 | + <tbody> | |
107 | + <tr v-for="(item, index) in listData" :key="index"> | |
108 | + <td>{{ item.buseo }}</td> | |
109 | + <td>{{ item.user }}</td> | |
110 | + <td>{{ item.startDate }} ~ {{ item.endDate }}</td> | |
111 | + <td :class="getStatusClass(item.status)"> | |
112 | + {{ item.status }} | |
113 | + </td> | |
114 | + </tr> | |
115 | + </tbody> | |
116 | + </table> | |
117 | + | |
118 | + </div> | |
119 | + <div class="pagination"> | |
120 | + <ul> | |
121 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
122 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
123 | + < | |
124 | + </li> | |
125 | + | |
126 | + <!-- 페이지 번호 --> | |
127 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
128 | + @click="changePage(page)"> | |
129 | + {{ page }} | |
130 | + </li> | |
131 | + | |
132 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
133 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
134 | + > | |
135 | + </li> | |
136 | + </ul> | |
137 | + </div> | |
138 | + </div> | |
139 | + </div> | |
140 | + </div> | |
141 | + | |
142 | + </div> | |
143 | + | |
144 | +</template> | |
145 | + | |
146 | +<script> | |
147 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
148 | +import HrPopup from "../../../component/Popup/HrPopup.vue"; | |
149 | +import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons-vue'; | |
150 | +export default { | |
151 | + data() { | |
152 | + return { | |
153 | + showPopup: false, | |
154 | + selectedname: '', | |
155 | + approvals: [], | |
156 | + require: "/client/resources/img/require.png", | |
157 | + h3icon: "/client/resources/img/h3icon.png", | |
158 | + photoicon: "/client/resources/img/photo_icon.png", | |
159 | + img1: "/client/resources/img/img.png", | |
160 | + icon1: "/client/resources/img/icon.png", | |
161 | + dateicon: "/client/resources/img/date.png", | |
162 | + startbtn: "/client/resources/img/start.png", | |
163 | + stopbtn: "/client/resources/img/stop.png", | |
164 | + moreicon: "/client/resources/img/more.png", | |
165 | + today: new Date().toLocaleDateString('ko-KR', { | |
166 | + year: 'numeric', | |
167 | + month: '2-digit', | |
168 | + day: '2-digit', | |
169 | + weekday: 'short', | |
170 | + }), | |
171 | + time: this.getCurrentTime(), | |
172 | + listData: [ | |
173 | + { | |
174 | + buseo: '', | |
175 | + user: '김철수', | |
176 | + startDate: '2025-05-01', | |
177 | + endDate: '2025-05-03', | |
178 | + status: '예약', // 사용중 | |
179 | + }, | |
180 | + { | |
181 | + buseo: '', | |
182 | + user: '이영희', | |
183 | + startDate: '2025-05-05', | |
184 | + endDate: '2025-05-06', | |
185 | + status: '예약', // 예약됨 | |
186 | + }, | |
187 | + { | |
188 | + buseo: '', | |
189 | + user: '박민호', | |
190 | + startDate: '2025-04-25', | |
191 | + endDate: '2025-04-28', | |
192 | + status: '반납', // 반납됨 | |
193 | + }, | |
194 | + ], | |
195 | + | |
196 | + } | |
197 | + }, | |
198 | + components: { | |
199 | + SearchOutlined, CloseCircleFilled, HrPopup | |
200 | + }, | |
201 | + methods: { | |
202 | + addApproval(selectedUser) { | |
203 | + this.approvals.push({ | |
204 | + name: selectedUser.name | |
205 | + }); | |
206 | + | |
207 | + this.selectedname = selectedUser.name; // 입력창에 표시 | |
208 | + this.showPopup = false; | |
209 | + }, | |
210 | + formatBudget(amount) { | |
211 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
212 | + }, | |
213 | + isPastPeriod(period) { | |
214 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
215 | + const endDateStr = period.split('~')[1]?.trim(); | |
216 | + if (!endDateStr) return false; | |
217 | + | |
218 | + const endDate = new Date(endDateStr); | |
219 | + const today = new Date(); | |
220 | + | |
221 | + // 현재 날짜보다 과거면 true | |
222 | + return endDate < today; | |
223 | + }, | |
224 | + getStatusClass(status) { | |
225 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
226 | + }, | |
227 | + getStatusClass(status) { | |
228 | + if (status === '예약') return 'status-pending'; | |
229 | + if (status === '반납') return 'status-approved'; | |
230 | + | |
231 | + // Default empty string | |
232 | + return ''; | |
233 | + }, | |
234 | + getCurrentTime() { | |
235 | + const now = new Date(); | |
236 | + const hours = String(now.getHours()).padStart(2, '0'); | |
237 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
238 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
239 | + return `${hours}:${minutes}:${seconds}`; | |
240 | + }, | |
241 | + getCategoryClass(category) { | |
242 | + switch (category) { | |
243 | + case '용역': return 'category-service'; | |
244 | + case '내부': return 'category-internal'; | |
245 | + case '국가과제': return 'category-government'; | |
246 | + default: return ''; | |
247 | + } | |
248 | + }, | |
249 | + }, | |
250 | + watch: { | |
251 | + | |
252 | + }, | |
253 | + computed: { | |
254 | + | |
255 | + }, | |
256 | + mounted() { | |
257 | + console.log('main mounted'); | |
258 | + setInterval(() => { | |
259 | + this.time = this.getCurrentTime(); | |
260 | + }, 1000); | |
261 | + } | |
262 | +} | |
263 | +</script> | |
264 | +<style scoped> | |
265 | +tr { | |
266 | + cursor: pointer; | |
267 | +} | |
268 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/asset/CardList.vue
... | ... | @@ -0,0 +1,209 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">사용현황</h2> | |
6 | + <div class="sch-form-wrap title-wrap"> | |
7 | + <h3><img :src="h3icon" alt="">예약현황</h3> | |
8 | + <div class="input-group"> | |
9 | + <select name="" id="" class="form-select"> | |
10 | + <option value="">전체</option> | |
11 | + <option value="">카드명</option> | |
12 | + <option value="">신청자</option> | |
13 | + </select> | |
14 | + <div class="sch-input"> | |
15 | + <input type="text" class="form-control"> | |
16 | + <button class="ico-sch"><SearchOutlined /></button> | |
17 | + </div> | |
18 | + </div> | |
19 | + </div> | |
20 | + | |
21 | + <!-- Table --> | |
22 | + <div class="tbl-wrap"> | |
23 | + <table id="myTable" class="tbl data"> | |
24 | + <!-- 동적으로 <th> 생성 --> | |
25 | + <thead> | |
26 | + <tr> | |
27 | + <th>카드명</th> | |
28 | + <th>부서</th> | |
29 | + <th>신청자</th> | |
30 | + <th>기간</th> | |
31 | + </tr> | |
32 | + </thead> | |
33 | + <!-- 동적으로 <td> 생성 --> | |
34 | + <tbody> | |
35 | + <tr v-for="(item, index) in listData" :key="index"> | |
36 | + <td>{{ item.name }}</td> | |
37 | + <td>{{ item.department }}</td> | |
38 | + <td>{{ item.user }}</td> | |
39 | + <td>{{ item.startDate }} ~ {{ item.endDate }}</td> | |
40 | + </tr> | |
41 | + </tbody> | |
42 | + </table> | |
43 | + | |
44 | + </div> | |
45 | + <div class="pagination"> | |
46 | + <ul> | |
47 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
48 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @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 }" @click="changePage(currentPage + 1)"> | |
60 | + > | |
61 | + </li> | |
62 | + </ul> | |
63 | + </div> | |
64 | + <div class="sch-form-wrap title-wrap"> | |
65 | + <h3><img :src="h3icon" alt="">사용이력</h3> | |
66 | + <div class="input-group"> | |
67 | + <select name="" id="" class="form-select"> | |
68 | + <option value="">전체</option> | |
69 | + <option value="">카드명</option> | |
70 | + <option value="">신청자</option> | |
71 | + </select> | |
72 | + <div class="sch-input"> | |
73 | + <input type="text" class="form-control"> | |
74 | + <button class="ico-sch"><SearchOutlined /></button> | |
75 | + </div> | |
76 | + </div> | |
77 | + </div> | |
78 | + | |
79 | + <!-- Table --> | |
80 | + <div class="tbl-wrap"> | |
81 | + <table id="myTable" class="tbl data"> | |
82 | + <!-- 동적으로 <th> 생성 --> | |
83 | + <thead> | |
84 | + <tr> | |
85 | + <th>카드명</th> | |
86 | + <th>부서</th> | |
87 | + <th>신청자</th> | |
88 | + <th>기간</th> | |
89 | + </tr> | |
90 | + </thead> | |
91 | + <!-- 동적으로 <td> 생성 --> | |
92 | + <tbody> | |
93 | + <tr v-for="(item, index) in listData" :key="index"> | |
94 | + <td>{{ item.name }}</td> | |
95 | + <td>{{ item.department }}</td> | |
96 | + <td>{{ item.user }}</td> | |
97 | + <td>{{ item.startDate }} ~ {{ item.endDate }}</td> | |
98 | + </tr> | |
99 | + </tbody> | |
100 | + </table> | |
101 | + | |
102 | + </div> | |
103 | + <div class="pagination"> | |
104 | + <ul> | |
105 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
106 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
107 | + < | |
108 | + </li> | |
109 | + | |
110 | + <!-- 페이지 번호 --> | |
111 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
112 | + @click="changePage(page)"> | |
113 | + {{ page }} | |
114 | + </li> | |
115 | + | |
116 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
117 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
118 | + > | |
119 | + </li> | |
120 | + </ul> | |
121 | + </div> | |
122 | + </div> | |
123 | + </div> | |
124 | + </div> | |
125 | +</template> | |
126 | + | |
127 | +<script> | |
128 | +import { ref } from 'vue'; | |
129 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
130 | +export default { | |
131 | + data() { | |
132 | + return { | |
133 | + currentPage: 1, | |
134 | + totalPages: 3, | |
135 | + photoicon: "/client/resources/img/photo_icon.png", | |
136 | + h3icon: "/client/resources/img/h3icon.png", | |
137 | + // 데이터 초기화 | |
138 | + years: [2023, 2024, 2025], // 연도 목록 | |
139 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
140 | + selectedYear: '', | |
141 | + selectedMonth: '', | |
142 | + listData: [ | |
143 | + { | |
144 | + name: '쏘렌토', | |
145 | + department: '총무부', | |
146 | + user: '김철수', | |
147 | + startDate: '2025-05-01', | |
148 | + endDate: '2025-05-10', | |
149 | + }, | |
150 | + { | |
151 | + name: '그랜저', | |
152 | + department: '영업부', | |
153 | + user: '이영희', | |
154 | + startDate: '2025-05-03', | |
155 | + endDate: '2025-05-04', | |
156 | + },], | |
157 | + filteredData: [], | |
158 | + }; | |
159 | + }, | |
160 | + computed: { | |
161 | + }, | |
162 | + components:{ | |
163 | + SearchOutlined | |
164 | + }, | |
165 | + methods: { | |
166 | + changePage(page) { | |
167 | + if (page < 1 || page > this.totalPages) return; | |
168 | + this.currentPage = page; | |
169 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
170 | + }, | |
171 | + async onClickSubmit() { | |
172 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
173 | + const { mutate, onDone, onError } = useMutation(mygql); | |
174 | + | |
175 | + try { | |
176 | + const result = await mutate(); | |
177 | + console.log(result); | |
178 | + } catch (error) { | |
179 | + console.error('Mutation error:', error); | |
180 | + } | |
181 | + }, | |
182 | + registerLeave() { | |
183 | + console.log("등록 버튼 클릭됨"); | |
184 | + | |
185 | + // Vue의 반응성 문제를 피하기 위해, 새로운 객체를 추가합니다. | |
186 | + this.DeptData = [ | |
187 | + ...this.DeptData, | |
188 | + { member: '', deptNM: '', acceptTerms: false } | |
189 | + ]; | |
190 | + | |
191 | + console.log(this.DeptData); // 배열 상태 출력 | |
192 | + }, | |
193 | + getStatusClass(status) { | |
194 | + if (status === '승인') return 'status-approved'; | |
195 | + if (status === '대기') return 'status-pending'; | |
196 | + return ''; | |
197 | + }, | |
198 | + | |
199 | + }, | |
200 | + created() { | |
201 | + }, | |
202 | + mounted() { | |
203 | + | |
204 | + | |
205 | + }, | |
206 | +}; | |
207 | +</script> | |
208 | + | |
209 | +<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/Manager/asset/asset.vue
+++ client/views/pages/Manager/asset/asset.vue
... | ... | @@ -1,14 +1,105 @@ |
1 | 1 |
<template> |
2 |
- <div class="services"> |
|
3 |
- <h2>Our Services</h2> |
|
4 |
- <p>We provide web development and design services.</p> |
|
5 |
- </div> |
|
6 |
- </template> |
|
7 |
- |
|
8 |
- <script> |
|
9 |
- </script> |
|
10 |
- |
|
11 |
- <style scoped> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo simple"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
8 |
+ </div> |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
13 |
+ </div> |
|
14 |
+ </div> |
|
15 |
+ </div> |
|
12 | 16 |
|
13 |
- </style> |
|
14 |
-(파일 끝에 줄바꿈 문자 없음) |
|
17 |
+ |
|
18 |
+ <details class="menu-box"> |
|
19 |
+ <summary><p>법인차량</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
20 |
+ <ul> |
|
21 |
+ <li> <router-link to="/CarList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
22 |
+ <p>사용현황</p> |
|
23 |
+ <div class="icon" v-if="isExactActive"> |
|
24 |
+ <img :src="menuicon" alt=""> |
|
25 |
+ </div> |
|
26 |
+ </router-link></li> |
|
27 |
+ <li> |
|
28 |
+ <router-link to="/CarInfoManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
29 |
+ <p>차량정보 관리</p> |
|
30 |
+ <div class="icon" v-if="isExactActive"> |
|
31 |
+ <img :src="menuicon" alt=""> |
|
32 |
+ </div> |
|
33 |
+ </router-link> |
|
34 |
+ </li> |
|
35 |
+ |
|
36 |
+ |
|
37 |
+ </ul> |
|
38 |
+ </details> |
|
39 |
+ <details class="menu-box"> |
|
40 |
+ <summary><p>법인카드</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
41 |
+ <ul> |
|
42 |
+ <li> <router-link to="/CardList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
43 |
+ <p>사용현황</p> |
|
44 |
+ <div class="icon" v-if="isExactActive"> |
|
45 |
+ <img :src="menuicon" alt=""> |
|
46 |
+ </div> |
|
47 |
+ </router-link></li> |
|
48 |
+ <li> <router-link to="/CardInfoManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
49 |
+ <p>카드정보 관리</p> |
|
50 |
+ <div class="icon" v-if="isExactActive"> |
|
51 |
+ <img :src="menuicon" alt=""> |
|
52 |
+ </div> |
|
53 |
+ </router-link></li> |
|
54 |
+ |
|
55 |
+ |
|
56 |
+ </ul> |
|
57 |
+ </details> |
|
58 |
+ </div> |
|
59 |
+ </div> |
|
60 |
+ <!-- End Page Title --> |
|
61 |
+ <div class="content"> |
|
62 |
+ <router-view></router-view> |
|
63 |
+ |
|
64 |
+ </div> |
|
65 |
+</template> |
|
66 |
+ |
|
67 |
+<script> |
|
68 |
+import { ref } from 'vue'; |
|
69 |
+ |
|
70 |
+export default { |
|
71 |
+ data() { |
|
72 |
+ return { |
|
73 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
74 |
+ menuicon: "/client/resources/img/menuicon.png", |
|
75 |
+ topmenuicon: "/client/resources/img/topmenuicon.png", |
|
76 |
+ // 데이터 초기화 |
|
77 |
+ years: [2023, 2024, 2025], // 연도 목록 |
|
78 |
+ months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
79 |
+ selectedYear: '', |
|
80 |
+ selectedMonth: '', |
|
81 |
+ DeptData: [ |
|
82 |
+ { member: '', deptNM: '', acceptTerms: false }, |
|
83 |
+ // 더 많은 데이터 추가... |
|
84 |
+ ], |
|
85 |
+ filteredData: [], |
|
86 |
+ }; |
|
87 |
+ }, |
|
88 |
+ computed: { |
|
89 |
+ }, |
|
90 |
+ methods: { |
|
91 |
+ |
|
92 |
+ // 페이지 변경 |
|
93 |
+ changePage(page) { |
|
94 |
+ this.currentPage = page; |
|
95 |
+ }, |
|
96 |
+ }, |
|
97 |
+ created() { |
|
98 |
+ }, |
|
99 |
+ mounted() { |
|
100 |
+ |
|
101 |
+ }, |
|
102 |
+}; |
|
103 |
+</script> |
|
104 |
+ |
|
105 |
+<style scoped></style> |
--- client/views/pages/Manager/attendance/AttendanceDetail.vue
+++ client/views/pages/Manager/attendance/AttendanceDetail.vue
... | ... | @@ -47,13 +47,29 @@ |
47 | 47 |
<div class="sch-form-wrap "> |
48 | 48 |
<div class="input-group"> |
49 | 49 |
<select name="" id="" class="form-select"> |
50 |
- <option value="">년도</option> |
|
51 |
- </select> |
|
50 |
+ <option :value="currentYear">{{ currentYear }}년</option> |
|
51 |
+ <option value="all">전체</option> |
|
52 |
+ <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> |
|
53 |
+ {{ year }}년 |
|
54 |
+ </option> |
|
55 |
+ </select> |
|
56 |
+ <select name="" id="" class="form-select"> |
|
57 |
+ <option :value="currentMonth">{{ currentMonth }}월</option> |
|
58 |
+ <option value="all">전체</option> |
|
59 |
+ <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> |
|
60 |
+ {{ month }}월 |
|
61 |
+ </option> |
|
62 |
+ </select> |
|
52 | 63 |
<select name="" id="" class="form-select"> |
53 |
- <option value="">월</option> |
|
54 |
- </select> |
|
55 |
- <select name="" id="" class="form-select"> |
|
56 |
- <option value="">구분</option> |
|
64 |
+ <option value="">전체</option> |
|
65 |
+ <option value="">지각</option> |
|
66 |
+ <option value="">조기퇴근</option> |
|
67 |
+ <option value="">결근</option> |
|
68 |
+ <option value="">출장</option> |
|
69 |
+ <option value="">대체휴가</option> |
|
70 |
+ <option value="">휴가</option> |
|
71 |
+ <option value="">공가</option> |
|
72 |
+ <option value="">병가</option> |
|
57 | 73 |
</select> |
58 | 74 |
</div> |
59 | 75 |
|
... | ... | @@ -163,12 +179,19 @@ |
163 | 179 |
|
164 | 180 |
<script> |
165 | 181 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
166 |
- |
|
182 |
+const currentYear = new Date().getFullYear(); |
|
183 |
+const currentMonth = new Date().getMonth() + 1; |
|
167 | 184 |
export default { |
168 | 185 |
data() { |
169 | 186 |
|
170 | 187 |
const today = new Date().toISOString().split('T')[0]; |
171 | 188 |
return { |
189 |
+ currentMonth, |
|
190 |
+ selectedMonth: currentMonth, |
|
191 |
+ remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), |
|
192 |
+ currentYear, |
|
193 |
+ selectedYear: currentYear, |
|
194 |
+ remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), |
|
172 | 195 |
photoicon: "/client/resources/img/img1.png", |
173 | 196 |
currentPage: 1, |
174 | 197 |
totalPages: 3, |
... | ... | @@ -203,30 +226,9 @@ |
203 | 226 |
SearchOutlined |
204 | 227 |
}, |
205 | 228 |
computed: { |
206 |
- // Pinia Store의 상태를 가져옵니다. |
|
207 |
- loginUser() { |
|
208 |
- const authStore = useAuthStore(); |
|
209 |
- return authStore.getLoginUser; |
|
210 |
- }, |
|
211 | 229 |
}, |
212 | 230 |
methods: { |
213 |
- // 폼 검증 메서드 |
|
214 |
- validateForm() { |
|
215 |
- // 필수 입력 필드 체크 |
|
216 |
- if ( |
|
217 |
- this.category && |
|
218 |
- this.startDate && |
|
219 |
- this.startTime && |
|
220 |
- this.endDate && |
|
221 |
- this.endTime && |
|
222 |
- this.dayCount > 0 && |
|
223 |
- this.reason.trim() !== "" |
|
224 |
- ) { |
|
225 |
- this.isFormValid = true; |
|
226 |
- } else { |
|
227 |
- this.isFormValid = false; |
|
228 |
- } |
|
229 |
- }, |
|
231 |
+ |
|
230 | 232 |
calculateDayCount() { |
231 | 233 |
const start = new Date(`${this.startDate}T${this.startTime}:00`); |
232 | 234 |
const end = new Date(`${this.endDate}T${this.endTime}:00`); |
... | ... | @@ -252,32 +254,9 @@ |
252 | 254 |
} |
253 | 255 |
} |
254 | 256 |
|
255 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
256 | 257 |
}, |
257 |
- handleSubmit() { |
|
258 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
259 |
- if (this.isFormValid) { |
|
260 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
261 |
- alert("승인 요청이 완료되었습니다."); |
|
262 |
- // 추가 처리 로직 (API 요청 등) |
|
263 |
- } else { |
|
264 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
265 |
- } |
|
266 |
- }, |
|
267 |
- |
|
268 |
- |
|
258 |
+ |
|
269 | 259 |
}, |
270 |
- mounted() { |
|
271 |
- // Load the saved form data when the page is loaded |
|
272 |
- this.loadFormData(); |
|
273 |
- }, |
|
274 |
- watch: { |
|
275 |
- startDate: 'calculateDayCount', |
|
276 |
- startTime: 'calculateDayCount', |
|
277 |
- endDate: 'calculateDayCount', |
|
278 |
- endTime: 'calculateDayCount', |
|
279 |
- reason: "validateForm", |
|
280 |
- category: 'category', |
|
281 |
- }, |
|
260 |
+ |
|
282 | 261 |
}; |
283 | 262 |
</script> |
--- client/views/pages/Manager/approval/ChuljangInsert.vue
+++ client/views/pages/Manager/attendance/BokmyeongInsert.vue
... | ... | @@ -1,49 +1,49 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="card"> |
3 | 3 |
<div class="card-body"> |
4 |
- <h2 class="card-title">출장 현황</h2> |
|
4 |
+ <h2 class="card-title">출장 복명서 등록</h2> |
|
5 | 5 |
<!-- Multi Columns Form --> |
6 |
- <form class="row g-3 pt-3 needs-validation" @submit.prevent="handleSubmit"> |
|
6 |
+ <form class="row g-3 pt-3 needs-validation detail" @submit.prevent="handleSubmit"> |
|
7 | 7 |
|
8 | 8 |
|
9 | 9 |
<div class="col-12"> |
10 | 10 |
<label for="where" class="form-label">출장구분</label> |
11 |
- <input type="text" class="form-control" id="where" v-model="where" /> |
|
11 |
+ <input type="text" class="form-control" id="where" v-model="where" readonly/> |
|
12 | 12 |
</div> |
13 | 13 |
<div class="col-12"> |
14 | 14 |
<label for="where" class="form-label">출장지</label> |
15 |
- <input type="text" class="form-control" id="where" v-model="where" /> |
|
15 |
+ <input type="text" class="form-control" id="where" v-model="where" readonly/> |
|
16 | 16 |
</div> |
17 | 17 |
<div class="col-12"> |
18 | 18 |
<label for="where" class="form-label">출장목적</label> |
19 |
- <input type="text" class="form-control" id="where" v-model="where" /> |
|
19 |
+ <input type="text" class="form-control" id="where" v-model="where" readonly/> |
|
20 | 20 |
</div> |
21 | 21 |
<div class="col-12"> |
22 | 22 |
<label for="where" class="form-label">출장기간</label> |
23 |
- <input type="text" class="form-control" id="where" v-model="where" /> |
|
23 |
+ <input type="text" class="form-control" id="where" v-model="where" readonly/> |
|
24 | 24 |
</div> |
25 | 25 |
|
26 | 26 |
<div class="col-12"> |
27 | 27 |
<label for="purpose" class="form-label">동행자</label> |
28 |
- <input type="text" class="form-control" id="purpose" v-model="purpose" /> |
|
28 |
+ <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> |
|
29 | 29 |
</div> |
30 | 30 |
<div class="col-12"> |
31 | 31 |
<label for="purpose" class="form-label">법인카드</label> |
32 |
- <input type="text" class="form-control" id="purpose" v-model="purpose" /> |
|
32 |
+ <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> |
|
33 | 33 |
</div> |
34 | 34 |
<div class="col-12"> |
35 | 35 |
<label for="purpose" class="form-label">법인차량</label> |
36 |
- <input type="text" class="form-control" id="purpose" v-model="purpose" /> |
|
36 |
+ <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> |
|
37 | 37 |
</div> |
38 | 38 |
|
39 | 39 |
<div class="col-12"> |
40 | 40 |
<label for="member" class="form-label"> |
41 | 41 |
승인자 |
42 |
- <button type="button" title="추가" @click="addApproval"> |
|
42 |
+ <button type="button" title="추가" @click="showPopup = true"> |
|
43 | 43 |
<PlusCircleFilled /> |
44 | 44 |
</button> |
45 | 45 |
</label> |
46 |
- |
|
46 |
+ <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval"/> |
|
47 | 47 |
<div class="approval-container"> |
48 | 48 |
<div v-for="(approval, index) in approvals" :key="index" class="d-flex gap-2 addapproval mb-2"> |
49 | 49 |
<select class="form-select" v-model="approval.category" style="width: 110px;"> |
... | ... | @@ -68,57 +68,55 @@ |
68 | 68 |
<div class="col-12 border-x"> |
69 | 69 |
<label for="member" class="form-label"> |
70 | 70 |
여비계산 |
71 |
- <button type="button" title="추가" @click="addApproval"> |
|
71 |
+ <button type="button" title="추가" @click="addReceipt"> |
|
72 | 72 |
<PlusCircleFilled /> |
73 | 73 |
</button> |
74 | 74 |
</label> |
75 | 75 |
|
76 | 76 |
<div class="approval-container"> |
77 |
- <div v-for="(approval, index) in approvals" :key="index" class="d-flex gap-2 addapproval mb-2"> |
|
78 |
- <select class="form-select" v-model="approval.category" style="width: 140px;"> |
|
79 |
- <option value="결재">개인결제</option> |
|
80 |
- <option value="전결">법인결제</option> |
|
77 |
+ <div v-for="(receipt, index) in receipts" :key="index" class="d-flex gap-2 addapproval mb-2"> |
|
78 |
+ <select class="form-select" style="width: 140px;"> |
|
79 |
+ <option value="">개인결제</option> |
|
80 |
+ <option value="">법인결제</option> |
|
81 | 81 |
</select> |
82 |
- <select class="form-select" v-model="approval.category" style="width: 110px;"> |
|
83 |
- <option value="결재">법인</option> |
|
84 |
- <option value="전결">개인</option> |
|
82 |
+ <select class="form-select" style="width: 110px;"> |
|
83 |
+ <option value="">법인</option> |
|
84 |
+ <option value="">개인</option> |
|
85 | 85 |
</select> |
86 |
- <select class="form-select" v-model="approval.category" style="width: 110px;"> |
|
87 |
- <option value="결재" selected>구분</option> |
|
88 |
- <option value="전결">개인</option> |
|
86 |
+ <select class="form-select" style="width: 110px;"> |
|
87 |
+ <option value="" selected>구분</option> |
|
88 |
+ <option value="">여비사용</option> |
|
89 | 89 |
</select> |
90 | 90 |
|
91 |
- <input type="text" class="form-control" v-model="approval.name" style="max-width: 150px;" /> |
|
92 |
- <div class=""> |
|
93 |
- <!-- 커스텀 업로드 버튼 --> |
|
94 |
- <button> |
|
95 |
- <label for="fileUpload" > |
|
96 |
- 영수증 첨부 |
|
97 |
- </label> |
|
98 |
- </button> |
|
91 |
+ <input type="text" class="form-control" placeholder="금액입력" style="max-width: 150px;" /> |
|
92 |
+ |
|
93 |
+ <!-- 커스텀 업로드 버튼 --> |
|
94 |
+ <label :for="'fileUpload-' + index" class="upload-label"> |
|
95 |
+ 영수증 첨부 |
|
96 |
+ </label> |
|
99 | 97 |
|
100 |
- <!-- 실제 파일 input (숨김 처리) --> |
|
101 |
- <input |
|
102 |
- id="fileUpload" |
|
103 |
- type="file" |
|
104 |
- @change="handleFileUpload" |
|
105 |
- class="hidden-file-input" |
|
106 |
- /> |
|
98 |
+ <!-- 실제 파일 input (숨김 처리) --> |
|
99 |
+ <input |
|
100 |
+ :id="'fileUpload-' + index" |
|
101 |
+ type="file" |
|
102 |
+ @change="handleFileUpload(index, $event)" |
|
103 |
+ class="hidden-file-input" |
|
104 |
+ /> |
|
107 | 105 |
|
108 |
- |
|
109 |
- </div> |
|
110 |
- <!-- 선택된 파일 이름 표시 --> |
|
111 |
- <span v-if="fileName" class="file-name">{{ fileName }}</span> |
|
112 |
- <button type="button" @click="removeApproval(index)" class="delete-button"> |
|
113 |
- <CloseOutlined /> |
|
114 |
- </button> |
|
106 |
+ |
|
107 |
+ <!-- 선택된 파일 이름 표시 --> |
|
108 |
+ <span v-if="receipt.fileName" class="file-name">{{ receipt.fileName }}</span> |
|
109 |
+ |
|
110 |
+ <button type="button" @click="removeReceipt(index)" class="delete-button"> |
|
111 |
+ <CloseOutlined /> |
|
112 |
+ </button> |
|
115 | 113 |
</div> |
116 | 114 |
</div> |
117 | 115 |
</div> |
118 | 116 |
|
119 | 117 |
</form> |
120 | 118 |
<div class="buttons"> |
121 |
- <button type="submit" class="btn primary">승인요청</button> |
|
119 |
+ <button type="submit" class="btn primary">등록</button> |
|
122 | 120 |
<button type="reset" class="btn secondary">취소</button> |
123 | 121 |
</div> |
124 | 122 |
|
... | ... | @@ -128,11 +126,12 @@ |
128 | 126 |
|
129 | 127 |
<script> |
130 | 128 |
import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; |
131 |
- |
|
129 |
+import HrPopup from '../../../component/Popup/HrPopup.vue'; |
|
132 | 130 |
export default { |
133 | 131 |
data() { |
134 | 132 |
const today = new Date().toISOString().split('T')[0]; |
135 | 133 |
return { |
134 |
+ showPopup: false, |
|
136 | 135 |
fileName: '', |
137 | 136 |
startDate: today, |
138 | 137 |
startTime: '09:00', |
... | ... | @@ -141,15 +140,18 @@ |
141 | 140 |
where: '', |
142 | 141 |
purpose: '', |
143 | 142 |
approvals: [ |
143 |
+ ], |
|
144 |
+ receipts: [ |
|
144 | 145 |
{ |
146 |
+ type: '개인결제', |
|
145 | 147 |
category: '결재', |
146 |
- name: '', |
|
148 |
+ category1: '구분', |
|
147 | 149 |
}, |
148 | 150 |
], |
149 | 151 |
}; |
150 | 152 |
}, |
151 | 153 |
components: { |
152 |
- PlusCircleFilled, CloseOutlined |
|
154 |
+ PlusCircleFilled, CloseOutlined,HrPopup |
|
153 | 155 |
}, |
154 | 156 |
computed: { |
155 | 157 |
loginUser() { |
... | ... | @@ -159,22 +161,37 @@ |
159 | 161 |
}, |
160 | 162 |
|
161 | 163 |
methods: { |
162 |
- handleFileUpload(event) { |
|
163 |
- const file = event.target.files[0]; |
|
164 |
- if (file) { |
|
165 |
- this.fileName = file.name; |
|
166 |
- } |
|
167 |
- }, |
|
168 |
- addApproval() { |
|
164 |
+ handleFileUpload(index, event) { |
|
165 |
+ const file = event.target.files[0]; |
|
166 |
+ if (file) { |
|
167 |
+ this.receipts[index].file = file; |
|
168 |
+ this.receipts[index].fileName = file.name; |
|
169 |
+ } |
|
170 |
+ }, |
|
171 |
+ addApproval(selectedUser) { |
|
169 | 172 |
this.approvals.push({ |
170 | 173 |
category: '결재', |
171 |
- name: '', |
|
174 |
+ name: selectedUser.name, // or other fields if needed |
|
172 | 175 |
}); |
176 |
+ this.showPopup = false; // 팝업 닫기 |
|
173 | 177 |
}, |
178 |
+ addReceipt() { |
|
179 |
+ this.receipts.push({ |
|
180 |
+ type: '개인결제', |
|
181 |
+ category: '', |
|
182 |
+ category1: '', |
|
183 |
+ name: '', |
|
184 |
+ file: null, |
|
185 |
+ fileName: '', |
|
186 |
+ }); |
|
187 |
+ }, |
|
174 | 188 |
// 승인자 삭제 |
175 | 189 |
removeApproval(index) { |
176 | 190 |
this.approvals.splice(index, 1); |
177 | 191 |
}, |
192 |
+ removeReceipt(index) { |
|
193 |
+ this.receipts.splice(index, 1); |
|
194 |
+}, |
|
178 | 195 |
validateForm() { |
179 | 196 |
// 필수 입력 필드 체크 |
180 | 197 |
if ( |
--- client/views/pages/Manager/attendance/ChuljangBokmyeongDetail.vue
+++ client/views/pages/Manager/attendance/ChuljangBokmyeongDetail.vue
... | ... | @@ -9,56 +9,64 @@ |
9 | 9 |
<table class="tbl data"> |
10 | 10 |
<tbody> |
11 | 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> |
|
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> |
|
20 | 23 |
</tbody> |
21 | 24 |
|
22 | 25 |
</table> |
23 | 26 |
</div> |
24 |
- <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
27 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
25 | 28 |
|
26 | 29 |
<div class="col-12 chuljang"> |
27 | 30 |
<label for="yourName" class="form-label">복명내용</label> |
28 |
- <input v-model="name" type="text" name="name" class="form-control textarea " id="yourName" readonly> |
|
31 |
+ <input v-model="name" type="text" name="name" class="form-control textarea " readonly> |
|
29 | 32 |
</div> |
30 | 33 |
<div class="col-12"> |
31 | 34 |
<label for="yourName" class="form-label">여비계산</label> |
32 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
35 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
33 | 36 |
</div> |
34 | 37 |
<div class="col-12 "> |
35 | 38 |
<label for="yourName" class="form-label">복명 신청일</label> |
36 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly placeholder="2025-01-01"> |
|
39 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly placeholder="2025-01-01"> |
|
37 | 40 |
</div> |
38 |
- <div class="col-12 border-x " :class="{ return: isReturned }"> |
|
41 |
+ <div class="col-12 border-x return"> |
|
39 | 42 |
<label for="yourName" class="form-label">반려사유</label> |
40 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly placeholder="2025-01-01"> |
|
43 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly placeholder="2025-01-01"> |
|
41 | 44 |
</div> |
42 | 45 |
|
43 | 46 |
|
44 | 47 |
</form> |
45 | 48 |
</div> |
46 |
- <div class="buttons"> |
|
47 |
- <button class="btn btn-red " type="submit">신청취소</button> |
|
48 |
- <button class="btn secondary" type="submit" @click="handleButtonClick">{{ isReturned ? '재신청' : '수정' }}</button> |
|
49 |
+ <div class="buttons"> |
|
50 |
+ <button class="btn primary" type="submit">승인</button> |
|
51 |
+ <button class="btn btn-red" type="submit" @click="showPopup = true">반려</button> |
|
52 |
+ <button class="btn btn-red" type="submit">신청취소</button> |
|
53 |
+ <button class="btn secondary" type="submit">재신청</button> |
|
54 |
+ <button class="btn secondary" type="submit">수정</button> |
|
49 | 55 |
<button class="btn tertiary " type="submit">목록</button> |
50 | 56 |
</div> |
57 |
+ <ReturnPopup v-if="showPopup" @close="showPopup = false"/> |
|
51 | 58 |
|
52 | 59 |
</div> |
53 | 60 |
</div> |
54 | 61 |
</template> |
55 | 62 |
|
56 | 63 |
<script> |
64 |
+import ReturnPopup from '../../../component/Popup/ReturnPopup.vue'; |
|
57 | 65 |
export default { |
58 | 66 |
data() { |
59 | 67 |
const today = new Date().toISOString().split('T')[0]; |
60 | 68 |
return { |
61 |
- isReturned: true, |
|
69 |
+ showPopup: false, |
|
62 | 70 |
startDate: today, |
63 | 71 |
startTime: "09:00", // 기본 시작 시간 09:00 |
64 | 72 |
endDate: today, |
... | ... | @@ -66,6 +74,10 @@ |
66 | 74 |
category: "", |
67 | 75 |
dayCount: 1, |
68 | 76 |
reason: "", // 사유 |
77 |
+ approvers: [ |
|
78 |
+ { position: '', name: '', date: '' }, |
|
79 |
+ { position: '', name: '', date: '' }, |
|
80 |
+ ], |
|
69 | 81 |
listData: [ |
70 | 82 |
{ |
71 | 83 |
type: '연차', |
... | ... | @@ -84,39 +96,17 @@ |
84 | 96 |
}], |
85 | 97 |
}; |
86 | 98 |
}, |
99 |
+ components: { |
|
100 |
+ ReturnPopup |
|
101 |
+ }, |
|
87 | 102 |
computed: { |
88 |
- // Pinia Store의 상태를 가져옵니다. |
|
89 |
- loginUser() { |
|
90 |
- const authStore = useAuthStore(); |
|
91 |
- return authStore.getLoginUser; |
|
92 |
- }, |
|
93 | 103 |
}, |
94 | 104 |
methods: { |
95 |
- // 폼 검증 메서드 |
|
96 |
- handleButtonClick() { |
|
97 |
- if (this.isReturned) { |
|
98 |
- // If "재신청", navigate to the desired page (ChuljangInsert.page) |
|
99 |
- this.$router.push({ name: 'ChuljangInsert' }); // Replace with the correct route name |
|
100 |
- } else { |
|
101 |
- // Handle the "수정" behavior here |
|
102 |
- console.log('수정 button clicked'); |
|
103 |
- } |
|
104 |
- }, |
|
105 |
- validateForm() { |
|
106 |
- // 필수 입력 필드 체크 |
|
107 |
- if ( |
|
108 |
- this.category && |
|
109 |
- this.startDate && |
|
110 |
- this.startTime && |
|
111 |
- this.endDate && |
|
112 |
- this.endTime && |
|
113 |
- this.dayCount > 0 && |
|
114 |
- this.reason.trim() !== "" |
|
115 |
- ) { |
|
116 |
- this.isFormValid = true; |
|
117 |
- } else { |
|
118 |
- this.isFormValid = false; |
|
119 |
- } |
|
105 |
+ hasAnyApprover() { |
|
106 |
+ return this.approvers.some( |
|
107 |
+ (approver) => |
|
108 |
+ approver.name?.trim() !== '' && approver.date?.trim() !== '' |
|
109 |
+ ); |
|
120 | 110 |
}, |
121 | 111 |
calculateDayCount() { |
122 | 112 |
const start = new Date(`${this.startDate}T${this.startTime}:00`); |
... | ... | @@ -143,32 +133,10 @@ |
143 | 133 |
} |
144 | 134 |
} |
145 | 135 |
|
146 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
136 |
+ |
|
147 | 137 |
}, |
148 |
- handleSubmit() { |
|
149 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
150 |
- if (this.isFormValid) { |
|
151 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
152 |
- alert("승인 요청이 완료되었습니다."); |
|
153 |
- // 추가 처리 로직 (API 요청 등) |
|
154 |
- } else { |
|
155 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
156 |
- } |
|
157 |
- }, |
|
138 |
+ |
|
158 | 139 |
|
159 |
- |
|
160 |
- }, |
|
161 |
- mounted() { |
|
162 |
- // Load the saved form data when the page is loaded |
|
163 |
- this.loadFormData(); |
|
164 |
- }, |
|
165 |
- watch: { |
|
166 |
- startDate: 'calculateDayCount', |
|
167 |
- startTime: 'calculateDayCount', |
|
168 |
- endDate: 'calculateDayCount', |
|
169 |
- endTime: 'calculateDayCount', |
|
170 |
- reason: "validateForm", |
|
171 |
- category: 'category', |
|
172 | 140 |
}, |
173 | 141 |
}; |
174 | 142 |
</script> |
--- client/views/pages/Manager/attendance/ChuljangDetailAll.vue
+++ client/views/pages/Manager/attendance/ChuljangDetailAll.vue
... | ... | @@ -21,56 +21,56 @@ |
21 | 21 |
|
22 | 22 |
</table> |
23 | 23 |
</div> |
24 |
- <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate > |
|
24 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate > |
|
25 | 25 |
<div class="col-12 "> |
26 | 26 |
<div class="col-12 border-x"> |
27 | 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" id="youremail" readonly > |
|
28 |
+ <input v-model="email" type="text" name="username" class="form-control" readonly > |
|
29 | 29 |
</div> |
30 | 30 |
|
31 | 31 |
<div class="col-12 border-x"> |
32 | 32 |
<label for="yourPassword" class="form-label">이름</label> |
33 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" readonly placeholder="주식회사 테이큰 소프트"> |
|
33 |
+ <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="주식회사 테이큰 소프트"> |
|
34 | 34 |
</div> |
35 | 35 |
</div> |
36 | 36 |
<div class="col-12"> |
37 | 37 |
<div class="col-12 border-x"> |
38 | 38 |
<label for="youremail" class="form-label">부서</label> |
39 |
- <input v-model="email" type="text" name="username" class="form-control" id="youremail" readonly placeholder="과장"> |
|
39 |
+ <input v-model="email" type="text" name="username" class="form-control" readonly placeholder="과장"> |
|
40 | 40 |
</div> |
41 | 41 |
|
42 | 42 |
<div class="col-12 border-x"> |
43 | 43 |
<label for="yourPassword" class="form-label">직급</label> |
44 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" readonly placeholder="팀장"> |
|
44 |
+ <input v-model="password" type="password" name="password" class="form-control" readonly placeholder="팀장"> |
|
45 | 45 |
</div> |
46 | 46 |
</div> |
47 | 47 |
<div class="col-12"> |
48 | 48 |
<label for="yourName" class="form-label">출장지</label> |
49 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly> |
|
49 |
+ <input v-model="name" type="text" name="name" class="form-control" readonly> |
|
50 | 50 |
</div> |
51 | 51 |
<div class="col-12"> |
52 | 52 |
<label for="yourName" class="form-label">출장목적</label> |
53 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
53 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
54 | 54 |
</div> |
55 | 55 |
<div class="col-12"> |
56 | 56 |
<label for="yourName" class="form-label">동행자</label> |
57 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
57 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
58 | 58 |
</div> |
59 | 59 |
<div class="col-12 chuljang"> |
60 | 60 |
<label for="yourName" class="form-label">품의내용</label> |
61 |
- <input v-model="name" type="text" name="name" class="form-control textarea " id="yourName" readonly> |
|
61 |
+ <input v-model="name" type="text" name="name" class="form-control textarea " readonly> |
|
62 | 62 |
</div> |
63 | 63 |
<div class="col-12"> |
64 | 64 |
<label for="yourName" class="form-label">법인카드</label> |
65 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
65 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
66 | 66 |
</div> |
67 | 67 |
<div class="col-12"> |
68 | 68 |
<label for="yourName" class="form-label">법인차량</label> |
69 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
69 |
+ <input v-model="name" type="text" name="name" class="form-control " readonly> |
|
70 | 70 |
</div> |
71 | 71 |
<div class="col-12 border-x"> |
72 | 72 |
<label for="yourName" class="form-label">품의 신청일</label> |
73 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
73 |
+ <input v-model="name" type="text" name="name" class="form-control " i readonly> |
|
74 | 74 |
</div> |
75 | 75 |
</form> |
76 | 76 |
</div> |
... | ... | @@ -92,7 +92,7 @@ |
92 | 92 |
|
93 | 93 |
</table> |
94 | 94 |
</div> |
95 |
- <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
95 |
+ <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
96 | 96 |
|
97 | 97 |
<div class="col-12 chuljang"> |
98 | 98 |
<label for="yourName" class="form-label">복명내용</label> |
... | ... | @@ -102,9 +102,13 @@ |
102 | 102 |
<label for="yourName" class="form-label">여비계산</label> |
103 | 103 |
<input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
104 | 104 |
</div> |
105 |
- <div class="col-12 border-x"> |
|
105 |
+ <div class="col-12 "> |
|
106 | 106 |
<label for="yourName" class="form-label">복명 신청일</label> |
107 | 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"> |
|
108 | 112 |
</div> |
109 | 113 |
|
110 | 114 |
|
... | ... | @@ -112,6 +116,7 @@ |
112 | 116 |
</div> |
113 | 117 |
<div class="buttons"> |
114 | 118 |
<button class="btn btn-red " type="submit">신청취소</button> |
119 |
+ <button class="btn secondary" type="submit">재신청</button> |
|
115 | 120 |
<button class="btn secondary" type="submit"> 수정</button> |
116 | 121 |
<button class="btn tertiary " type="submit">목록</button> |
117 | 122 |
</div> |
... | ... | @@ -215,18 +220,6 @@ |
215 | 220 |
}, |
216 | 221 |
|
217 | 222 |
|
218 |
- }, |
|
219 |
- mounted() { |
|
220 |
- // Load the saved form data when the page is loaded |
|
221 |
- this.loadFormData(); |
|
222 |
- }, |
|
223 |
- watch: { |
|
224 |
- startDate: 'calculateDayCount', |
|
225 |
- startTime: 'calculateDayCount', |
|
226 |
- endDate: 'calculateDayCount', |
|
227 |
- endTime: 'calculateDayCount', |
|
228 |
- reason: "validateForm", |
|
229 |
- category: 'category', |
|
230 | 223 |
}, |
231 | 224 |
}; |
232 | 225 |
</script> |
+++ client/views/pages/Manager/attendance/ChuljangInsert.vue
... | ... | @@ -0,0 +1,340 @@ |
1 | +<template> | |
2 | + <div class="card"> | |
3 | + <div class="card-body"> | |
4 | + <h2 class="card-title">출장 신청</h2> | |
5 | + <p class="require"><img :src="require" alt=""> 필수입력</p> | |
6 | + <!-- Multi Columns Form --> | |
7 | + <form class="row g-3 pt-3 needs-validation" @submit.prevent="handleSubmit"> | |
8 | + | |
9 | + | |
10 | + <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> | |
16 | + </div> | |
17 | + <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" /> | |
20 | + </div> | |
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" /> | |
24 | + </div> | |
25 | + <div class="col-12"> | |
26 | + <label for="where" class="form-label"><p>출장기간<p class="require"><img :src="require" alt=""></p></p></label> | |
27 | + <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 | + ~ | |
43 | + </div> | |
44 | + <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> | |
59 | + </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 | + </div> | |
64 | + </div> | |
65 | + | |
66 | + <div class="col-12"> | |
67 | + <label for="purpose" class="form-label">동행자 <button type="button" title="추가" @click="showPopup1 = true"> | |
68 | + <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"> | |
76 | + <CloseOutlined /> | |
77 | + </button> | |
78 | + </form> | |
79 | + </div> | |
80 | + </div> | |
81 | + | |
82 | + <div class="col-12"> | |
83 | + <label for="member" class="form-label"> | |
84 | + <p>승인자<button type="button" title="추가" @click="showPopup = true"> | |
85 | + <PlusCircleFilled /> | |
86 | + </button><p class="require"><img :src="require" alt=""></p></p> | |
87 | + | |
88 | + </label> | |
89 | + <HrPopup v-if="showPopup" @close="showPopup = false" @select="addApproval"/> | |
90 | + <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> | |
105 | + </div> | |
106 | + </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" /> | |
110 | + </div> | |
111 | + <div class="col-12"> | |
112 | + <label for="purpose" class="form-label">법인카드 <button type="button" title="추가" @click="showPopup3 = true"> | |
113 | + <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 | + | |
119 | + <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"> | |
122 | + <CloseOutlined /> | |
123 | + </button> | |
124 | + </form> | |
125 | + </div> | |
126 | + </div> | |
127 | + </div> | |
128 | + <div class="col-12 border-x"> | |
129 | + <label for="purpose" class="form-label">법인차량 <button type="button" title="추가" @click="showPopup2 = true"> | |
130 | + <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> | |
138 | + </select> | |
139 | + <button type="button" @click="removeCar(index)" class="delete-button"> | |
140 | + <CloseOutlined /> | |
141 | + </button> | |
142 | + </div> | |
143 | + </div> | |
144 | + </div> | |
145 | + | |
146 | + </form> | |
147 | + <div class="buttons"> | |
148 | + <button type="submit" class="btn primary">신청</button> | |
149 | + <button type="reset" class="btn secondary">취소</button> | |
150 | + </div> | |
151 | + | |
152 | + </div> | |
153 | + </div> | |
154 | +</template> | |
155 | + | |
156 | +<script> | |
157 | +import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; | |
158 | +import HrPopup from '../../../component/Popup/HrPopup.vue'; | |
159 | +import CorpCarPopup from '../../../component/Popup/CorpCarPopup.vue'; | |
160 | +import CorpCardPopup from '../../../component/Popup/CorpCardPopup.vue'; | |
161 | +export default { | |
162 | + data() { | |
163 | + const today = new Date().toISOString().split('T')[0]; | |
164 | + return { | |
165 | + showPopup: false, | |
166 | + showPopup1: false, | |
167 | + showPopup2: false, | |
168 | + showPopup3: false, | |
169 | + 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 | + ], | |
192 | + }; | |
193 | + }, | |
194 | + components: { | |
195 | + PlusCircleFilled, CloseOutlined, HrPopup, CorpCarPopup, CorpCardPopup | |
196 | + }, | |
197 | + computed: { | |
198 | + loginUser() { | |
199 | + const authStore = useAuthStore(); | |
200 | + return authStore.getLoginUser; | |
201 | + }, | |
202 | + }, | |
203 | + | |
204 | + methods: { | |
205 | + handleFileUpload(event) { | |
206 | + const file = event.target.files[0]; | |
207 | + if (file) { | |
208 | + this.fileName = file.name; | |
209 | + } | |
210 | + }, | |
211 | + addApproval(selectedUser) { | |
212 | + this.approvals.push({ | |
213 | + category: '결재', | |
214 | + name: selectedUser.name, // or other fields if needed | |
215 | + }); | |
216 | + this.showPopup = false; // 팝업 닫기 | |
217 | + }, | |
218 | + addMember(selectedUser) { | |
219 | + this.members.push({ | |
220 | + category: '결재', | |
221 | + name: selectedUser.name, // or other fields if needed | |
222 | + }); | |
223 | + this.showPopup1 = false; // 팝업 닫기 | |
224 | + }, | |
225 | + addCard(selectedCard) { | |
226 | + this.cards.push({ | |
227 | + category: '결재', | |
228 | + name: selectedCard.name, // or other fields if needed | |
229 | + }); | |
230 | + this.showPopup3 = false; // 팝업 닫기 | |
231 | + }, | |
232 | + addCar(selectedCar) { | |
233 | + this.cars.push({ | |
234 | + category: '결재', | |
235 | + name: selectedCar.name, // or other fields if needed | |
236 | + }); | |
237 | + this.showPopup2 = false; // 팝업 닫기 | |
238 | + }, | |
239 | + addReceipt() { | |
240 | + this.receipts.push({ | |
241 | + type: '개인결제', | |
242 | + category: '결재', | |
243 | + category1: '', | |
244 | + name: '', | |
245 | + file: null, | |
246 | + }); | |
247 | +}, | |
248 | + // 승인자 삭제 | |
249 | + removeApproval(index) { | |
250 | + this.approvals.splice(index, 1); | |
251 | + }, | |
252 | + removeMember(index) { | |
253 | + this.members.splice(index, 1); | |
254 | + }, | |
255 | + removeCard(index) { | |
256 | + this.cards.splice(index, 1); | |
257 | + }, | |
258 | + removeCar(index) { | |
259 | + this.cars.splice(index, 1); | |
260 | + }, | |
261 | + removeReceipt(index) { | |
262 | + this.receipts.splice(index, 1); | |
263 | +}, | |
264 | + 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; | |
277 | + } | |
278 | + }, | |
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 | + | |
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); // 시간 조건이 기준에 맞을 경우 | |
294 | + } | |
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 | + | |
304 | + this.validateForm(); // dayCount 변경 후 폼 재검증 | |
305 | + }, | |
306 | + handleSubmit() { | |
307 | + this.validateForm(); // 제출 시 유효성 확인 | |
308 | + if (this.isFormValid) { | |
309 | + localStorage.setItem('ChuljangFormData', JSON.stringify(this.$data)); | |
310 | + alert("승인 요청이 완료되었습니다."); | |
311 | + // 추가 처리 로직 (API 요청 등) | |
312 | + } else { | |
313 | + alert("모든 필드를 올바르게 작성해주세요."); | |
314 | + } | |
315 | + }, | |
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 | + }, | |
335 | +}; | |
336 | +</script> | |
337 | + | |
338 | +<style scoped> | |
339 | +/* 필요한 스타일 추가 */ | |
340 | +</style> |
--- client/views/pages/Manager/attendance/ChuljangPumuiDetail.vue
+++ client/views/pages/Manager/attendance/ChuljangPumuiDetail.vue
... | ... | @@ -1,94 +1,103 @@ |
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> |
|
5 |
+ |
|
6 | 6 |
<div class="form-card"> |
7 | 7 |
<h1>출장품의서</h1> |
8 | 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> |
|
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> |
|
21 | 23 |
|
22 |
- </table> |
|
24 |
+ </tbody> |
|
25 |
+ |
|
26 |
+ </table> |
|
23 | 27 |
</div> |
24 |
- <form class="row g-3 needs-validation " :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" id="youremail" 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" id="yourPassword" 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" id="youremail" 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" id="yourPassword" 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" id="yourName" readonly> |
|
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> |
|
50 | 34 |
</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 " id="yourName" 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 " id="yourName" 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 " id="yourName" 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 " id="yourName" 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 " id="yourName" readonly> |
|
70 |
- </div> |
|
71 |
- <div class="col-12"> |
|
72 |
- <label for="yourName" class="form-label">품의 신청일</label> |
|
73 |
- <input v-model="name" type="text" name="name" class="form-control " id="yourName" readonly> |
|
74 |
- </div> |
|
75 |
- <div class="col-12 border-x" :class="{ return: isReturned }"> |
|
76 |
- <label for="yourName" class="form-label">반려사유</label> |
|
77 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" readonly placeholder="2025-01-01"> |
|
78 |
- </div> |
|
79 |
- |
|
80 |
- |
|
81 |
- </form> |
|
82 |
- </div> |
|
83 |
- <div class="buttons"> |
|
84 |
- <button class="btn btn-red " type="submit">신청취소</button> |
|
85 |
- <button class="btn secondary" type="submit"> {{ isReturned ? '재신청' : '수정' }}</button> |
|
86 |
- <button class="btn primary" type="submit" v-if="!isReturned">복명서 작성</button> |
|
87 |
- <button class="btn tertiary " type="submit">목록</button> |
|
88 |
- </div> |
|
89 | 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> |
|
90 | 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 |
+ |
|
91 | 99 |
</div> |
100 |
+ </div> |
|
92 | 101 |
</template> |
93 | 102 |
|
94 | 103 |
<script> |
... | ... | @@ -96,108 +105,82 @@ |
96 | 105 |
data() { |
97 | 106 |
const today = new Date().toISOString().split('T')[0]; |
98 | 107 |
return { |
99 |
- isReturned: true, |
|
108 |
+ showPopup: false, |
|
100 | 109 |
startDate: today, |
101 | 110 |
startTime: "09:00", // 기본 시작 시간 09:00 |
102 | 111 |
endDate: today, |
103 | 112 |
endTime: "18:00", // 기본 종료 시간 18:00 |
104 |
- category: "", |
|
105 |
- dayCount: 1, |
|
113 |
+ category: "", |
|
114 |
+ dayCount: 1, |
|
106 | 115 |
reason: "", // 사유 |
116 |
+ approvers: [ |
|
117 |
+ { position: '', name: '', date: '' }, |
|
118 |
+ { position: '', name: '', date: '' }, |
|
119 |
+ ], |
|
107 | 120 |
listData: [ |
108 |
- { |
|
109 |
- type: '연차', |
|
110 |
- approvalType: '결재', |
|
111 |
- applicant: '홍길동', |
|
112 |
- period: '2025-05-10 ~ 2025-15-03', |
|
113 |
- requestDate: '2025-04-25', |
|
114 |
- status: '대기' |
|
115 |
- }, { |
|
116 |
- type: '반차', |
|
117 |
- approvalType: '전결', |
|
118 |
- applicant: '홍길동', |
|
119 |
- period: '2025-05-01 ~ 2025-05-03', |
|
120 |
- requestDate: '2025-04-25', |
|
121 |
- status: '승인' |
|
122 |
- }], |
|
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 |
+ }], |
|
123 | 136 |
}; |
124 | 137 |
}, |
125 | 138 |
computed: { |
126 |
- // Pinia Store의 상태를 가져옵니다. |
|
127 |
- loginUser() { |
|
128 |
- const authStore = useAuthStore(); |
|
129 |
- return authStore.getLoginUser; |
|
130 |
- }, |
|
131 | 139 |
}, |
132 | 140 |
methods: { |
133 |
- // 폼 검증 메서드 |
|
134 |
- validateForm() { |
|
135 |
- // 필수 입력 필드 체크 |
|
136 |
- if ( |
|
137 |
- this.category && |
|
138 |
- this.startDate && |
|
139 |
- this.startTime && |
|
140 |
- this.endDate && |
|
141 |
- this.endTime && |
|
142 |
- this.dayCount > 0 && |
|
143 |
- this.reason.trim() !== "" |
|
144 |
- ) { |
|
145 |
- this.isFormValid = true; |
|
146 |
- } else { |
|
147 |
- this.isFormValid = false; |
|
148 |
- } |
|
141 |
+ goToBokmyeongInsert() { |
|
142 |
+ this.$router.push('/BokmyeongInsert.page'); |
|
143 |
+ }, |
|
144 |
+ hasAnyApprover() { |
|
145 |
+ return this.approvers.some( |
|
146 |
+ (approver) => |
|
147 |
+ approver.name?.trim() !== '' && approver.date?.trim() !== '' |
|
148 |
+ ); |
|
149 | 149 |
}, |
150 | 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 |
- } |
|
151 |
+ const start = new Date(`${this.startDate}T${this.startTime}:00`); |
|
152 |
+ const end = new Date(`${this.endDate}T${this.endTime}:00`); |
|
174 | 153 |
|
175 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
176 |
- }, |
|
177 |
- handleSubmit() { |
|
178 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
179 |
- if (this.isFormValid) { |
|
180 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
181 |
- alert("승인 요청이 완료되었습니다."); |
|
182 |
- // 추가 처리 로직 (API 요청 등) |
|
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 |
+ } |
|
183 | 166 |
} else { |
184 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
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 |
+ } |
|
185 | 173 |
} |
174 |
+ |
|
186 | 175 |
}, |
187 |
- |
|
188 |
- |
|
189 |
- }, |
|
190 |
- mounted() { |
|
191 |
- // Load the saved form data when the page is loaded |
|
192 |
- this.loadFormData(); |
|
193 |
- }, |
|
194 |
- watch: { |
|
195 |
- startDate: 'calculateDayCount', |
|
196 |
- startTime: 'calculateDayCount', |
|
197 |
- endDate: 'calculateDayCount', |
|
198 |
- endTime: 'calculateDayCount', |
|
199 |
- reason: "validateForm", |
|
200 |
- category: 'category', |
|
176 |
+ |
|
177 |
+ |
|
178 |
+ |
|
201 | 179 |
}, |
202 | 180 |
}; |
203 | 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
... | ... | @@ -7,11 +7,29 @@ |
7 | 7 |
<div class="sch-form-wrap"> |
8 | 8 |
<div class="input-group"> |
9 | 9 |
<select name="" id="" class="form-select"> |
10 |
- <option value="">년도</option> |
|
11 |
- </select> |
|
12 |
- <select name="" id="" class="form-select"> |
|
13 |
- <option value="">월</option> |
|
14 |
- </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> |
|
20 |
+ </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> |
|
32 |
+ </select> |
|
15 | 33 |
</div> |
16 | 34 |
</div> |
17 | 35 |
<!-- Table --> |
... | ... | @@ -31,14 +49,26 @@ |
31 | 49 |
</thead> |
32 | 50 |
<!-- 동적으로 <td> 생성 --> |
33 | 51 |
<tbody> |
34 |
- <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastPeriod(item.period) }" > |
|
52 |
+ <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPast(item) }" @click="handleClick(item)"> |
|
35 | 53 |
<td>{{ item.type }}</td> |
36 | 54 |
<td>{{ item.where }}</td> |
37 | 55 |
<td>{{ item.purpose }}</td> |
38 |
- <td @click="goToPage('all')">{{ item.period }}</td> |
|
39 |
- <td :class="getStatusClass(item.pumuiStatue)" @click="goToPage('품의서')">{{ item.pumuiStatue }}</td> |
|
40 |
- <td :class="getBokmyeongClass(item.bokmyeong)" @click="goToPage('복명서')">{{ item.bokmyeong }}</td> |
|
41 |
- <td :class="getStatusClass(item.status)">{{ item.status }}</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> |
|
42 | 72 |
</tr> |
43 | 73 |
</tbody> |
44 | 74 |
</table> |
... | ... | @@ -72,9 +102,17 @@ |
72 | 102 |
<script> |
73 | 103 |
import { ref } from 'vue'; |
74 | 104 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
105 |
+const currentYear = new Date().getFullYear(); |
|
106 |
+const currentMonth = new Date().getMonth() + 1; |
|
75 | 107 |
export default { |
76 | 108 |
data() { |
77 | 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), |
|
78 | 116 |
showOptions: false, |
79 | 117 |
currentPage: 1, |
80 | 118 |
totalPages: 3, |
... | ... | @@ -92,6 +130,24 @@ |
92 | 130 |
period: '2025-05-10 ~ 2025-05-23', |
93 | 131 |
pumuiStatue: '대기', |
94 | 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: '등록', |
|
95 | 151 |
status: '대기' |
96 | 152 |
}, { |
97 | 153 |
type: '연차', |
... | ... | @@ -100,7 +156,7 @@ |
100 | 156 |
period: '2025-05-10 ~ 2025-05-10', |
101 | 157 |
pumuiStatue: '승인', |
102 | 158 |
bokmyeong: '등록', |
103 |
- status: '대기' |
|
159 |
+ status: '승인' |
|
104 | 160 |
},], |
105 | 161 |
filteredData: [], |
106 | 162 |
}; |
... | ... | @@ -130,15 +186,6 @@ |
130 | 186 |
console.error('Mutation error:', error); |
131 | 187 |
} |
132 | 188 |
}, |
133 |
- goToPage(type) { |
|
134 |
- if (type === '품의서') { |
|
135 |
- this.$router.push('/ChuljangPumuiDetail.page'); |
|
136 |
- } else if (type === 'all') { |
|
137 |
- this.$router.push('/ChuljangDetailAll.page'); |
|
138 |
- } else if (type === '복명서') { |
|
139 |
- this.$router.push('/ChuljangBokmyeongDetail.page'); |
|
140 |
- } |
|
141 |
-}, |
|
142 | 189 |
|
143 | 190 |
// 상태에 따른 클래스 반환 메소드 |
144 | 191 |
getStatusClass(status) { |
... | ... | @@ -160,17 +207,30 @@ |
160 | 207 |
if (bokmyeong === '미등록') return 'status-pending'; |
161 | 208 |
return ''; |
162 | 209 |
}, |
163 |
- isPastPeriod(period) { |
|
164 |
- // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 |
|
165 |
- const endDateStr = period.split('~')[1]?.trim(); |
|
166 |
- if (!endDateStr) return false; |
|
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 |
+ ); |
|
167 | 222 |
|
168 |
- const endDate = new Date(endDateStr); |
|
169 |
- const today = new Date(); |
|
170 |
- |
|
171 |
- // 현재 날짜보다 과거면 true |
|
172 |
- return endDate < today; |
|
173 |
- } |
|
223 |
+ if (item.bokmyeong === '등록') { |
|
224 |
+ this.$router.push('/ChuljangDetailAll.page'); |
|
225 |
+ } else if (isCasePumui) { |
|
226 |
+ this.$router.push('/ChuljangPumuiDetail.page'); |
|
227 |
+ } else { |
|
228 |
+ console.log('이동 조건이 아닙니다.'); |
|
229 |
+ } |
|
230 |
+}, |
|
231 |
+ handleBokmyeongClick(item) { |
|
232 |
+ this.$router.push('/ChuljangBokmyeongDetail.page'); |
|
233 |
+ }, |
|
174 | 234 |
}, |
175 | 235 |
created() { |
176 | 236 |
}, |
... | ... | @@ -178,6 +238,7 @@ |
178 | 238 |
|
179 | 239 |
|
180 | 240 |
}, |
241 |
+ |
|
181 | 242 |
}; |
182 | 243 |
</script> |
183 | 244 |
|
--- client/views/pages/Manager/attendance/buseoAttendance.vue
+++ client/views/pages/Manager/attendance/buseoAttendance.vue
... | ... | @@ -6,16 +6,24 @@ |
6 | 6 |
<div class="sch-form-wrap"> |
7 | 7 |
<div class="input-group"> |
8 | 8 |
<select name="" id="" class="form-select"> |
9 |
- <option value="">년도</option> |
|
10 |
- </select> |
|
11 |
- <select name="" id="" class="form-select"> |
|
12 |
- <option value="">월</option> |
|
13 |
- </select> |
|
9 |
+ <option :value="currentYear">{{ currentYear }}년</option> |
|
10 |
+ <option value="all">전체</option> |
|
11 |
+ <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> |
|
12 |
+ {{ year }}년 |
|
13 |
+ </option> |
|
14 |
+ </select> |
|
15 |
+ <select name="" id="" class="form-select"> |
|
16 |
+ <option :value="currentMonth">{{ currentMonth }}월</option> |
|
17 |
+ <option value="all">전체</option> |
|
18 |
+ <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> |
|
19 |
+ {{ month }}월 |
|
20 |
+ </option> |
|
21 |
+ </select> |
|
14 | 22 |
<select name="" id="" class="form-select"> |
15 | 23 |
<option value="">구분</option> |
16 | 24 |
</select> |
17 | 25 |
<div class="sch-input"> |
18 |
- <input type="text" class="form-control"> |
|
26 |
+ <input type="text" class="form-control" placeholder="직원명"> |
|
19 | 27 |
<button class="ico-sch"><SearchOutlined /></button> |
20 | 28 |
</div> |
21 | 29 |
</div> |
... | ... | @@ -101,9 +109,17 @@ |
101 | 109 |
<script> |
102 | 110 |
import { ref } from 'vue'; |
103 | 111 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
112 |
+const currentYear = new Date().getFullYear(); |
|
113 |
+const currentMonth = new Date().getMonth() + 1; |
|
104 | 114 |
export default { |
105 | 115 |
data() { |
106 | 116 |
return { |
117 |
+ currentMonth, |
|
118 |
+ selectedMonth: currentMonth, |
|
119 |
+ remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), |
|
120 |
+ currentYear, |
|
121 |
+ selectedYear: currentYear, |
|
122 |
+ remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), |
|
107 | 123 |
showOptions: false, |
108 | 124 |
currentPage: 1, |
109 | 125 |
totalPages: 3, |
--- client/views/pages/Manager/attendance/hyugaStatue.vue
+++ client/views/pages/Manager/attendance/hyugaStatue.vue
... | ... | @@ -8,9 +8,19 @@ |
8 | 8 |
<div class="form-conts"> |
9 | 9 |
<div class="form-conts datepicker-conts"> |
10 | 10 |
<div class="datepicker-input"> |
11 |
- <input type="date" class="form-control datepicker cal" placeholder="YYYY.MM.DD" id="cal" style="max-width: 200px;"> |
|
12 |
- <mark>~</mark> |
|
13 |
- <input type="date" class="form-control datepicker cal" placeholder="YYYY.MM.DD" id="cal" style="max-width: 200px;"> |
|
11 |
+ <input |
|
12 |
+ type="date" |
|
13 |
+ class="form-control datepicker cal" |
|
14 |
+ :value="startDate" |
|
15 |
+ style="max-width: 200px;" |
|
16 |
+ /> |
|
17 |
+ <mark>~</mark> |
|
18 |
+ <input |
|
19 |
+ type="date" |
|
20 |
+ class="form-control datepicker cal" |
|
21 |
+ :value="endDate" |
|
22 |
+ style="max-width: 200px;" |
|
23 |
+ /> |
|
14 | 24 |
</div> |
15 | 25 |
</div> |
16 | 26 |
</div> |
... | ... | @@ -112,7 +122,11 @@ |
112 | 122 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
113 | 123 |
export default { |
114 | 124 |
data() { |
125 |
+ const today = new Date(); |
|
126 |
+ const formattedToday = today.toISOString().split('T')[0]; // 'YYYY-MM-DD' |
|
115 | 127 |
return { |
128 |
+ startDate: '2025-01-01', |
|
129 |
+ endDate: formattedToday, |
|
116 | 130 |
showOptions: false, |
117 | 131 |
currentPage: 1, |
118 | 132 |
totalPages: 3, |
--- client/views/pages/Manager/attendance/myAttendance.vue
+++ client/views/pages/Manager/attendance/myAttendance.vue
... | ... | @@ -7,19 +7,35 @@ |
7 | 7 |
<div class="flex"> |
8 | 8 |
<div class="sub flex"><img :src="dateicon" alt=""><p class="date">{{ today }}</p></div> |
9 | 9 |
<div class="buttons"> |
10 |
- <button><img :src="startbtn" alt=""></button> |
|
11 |
- <button><img :src="stopbtn" alt=""></button> |
|
10 |
+ <button @click="handleStartClick"><img :src="startbtn" alt=""></button> |
|
11 |
+ <button @click="handleEndClick"><img :src="stopbtn" alt=""></button> |
|
12 | 12 |
</div> |
13 | 13 |
</div> |
14 | 14 |
<div class="input-group"> |
15 | 15 |
<select name="" id="" class="form-select"> |
16 |
- <option value="">년도</option> |
|
17 |
- </select> |
|
16 |
+ <option :value="currentYear">{{ currentYear }}년</option> |
|
17 |
+ <option value="all">전체</option> |
|
18 |
+ <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> |
|
19 |
+ {{ year }}년 |
|
20 |
+ </option> |
|
21 |
+ </select> |
|
22 |
+ <select name="" id="" class="form-select"> |
|
23 |
+ <option :value="currentMonth">{{ currentMonth }}월</option> |
|
24 |
+ <option value="all">전체</option> |
|
25 |
+ <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> |
|
26 |
+ {{ month }}월 |
|
27 |
+ </option> |
|
28 |
+ </select> |
|
18 | 29 |
<select name="" id="" class="form-select"> |
19 |
- <option value="">월</option> |
|
20 |
- </select> |
|
21 |
- <select name="" id="" class="form-select"> |
|
22 |
- <option value="">부서</option> |
|
30 |
+ <option value="">전체</option> |
|
31 |
+ <option value="">지각</option> |
|
32 |
+ <option value="">조기퇴근</option> |
|
33 |
+ <option value="">결근</option> |
|
34 |
+ <option value="">출장</option> |
|
35 |
+ <option value="">대체휴가</option> |
|
36 |
+ <option value="">휴가</option> |
|
37 |
+ <option value="">공가</option> |
|
38 |
+ <option value="">병가</option> |
|
23 | 39 |
</select> |
24 | 40 |
</div> |
25 | 41 |
|
... | ... | @@ -90,7 +106,7 @@ |
90 | 106 |
</colgroup> |
91 | 107 |
<thead> |
92 | 108 |
<tr> |
93 |
- <th>연차 </th> |
|
109 |
+ <th>구분 </th> |
|
94 | 110 |
<th>내용</th> |
95 | 111 |
</tr> |
96 | 112 |
</thead> |
... | ... | @@ -98,7 +114,7 @@ |
98 | 114 |
<tbody> |
99 | 115 |
<tr v-for="(item, index) in listData" :key="index" > |
100 | 116 |
<td>{{ item.type }}</td> |
101 |
- <td>{{ item.content }}</td> |
|
117 |
+ <td style="text-align: left !important;">{{ item.content }}</td> |
|
102 | 118 |
</tr> |
103 | 119 |
</tbody> |
104 | 120 |
</table> |
... | ... | @@ -130,11 +146,19 @@ |
130 | 146 |
|
131 | 147 |
<script> |
132 | 148 |
import { SearchOutlined } from '@ant-design/icons-vue'; |
149 |
+const currentYear = new Date().getFullYear(); |
|
150 |
+const currentMonth = new Date().getMonth() + 1; |
|
133 | 151 |
export default { |
134 | 152 |
data() { |
135 | 153 |
|
136 | 154 |
const today = new Date().toISOString().split('T')[0]; |
137 | 155 |
return { |
156 |
+ currentMonth, |
|
157 |
+ selectedMonth: currentMonth, |
|
158 |
+ remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), |
|
159 |
+ currentYear, |
|
160 |
+ selectedYear: currentYear, |
|
161 |
+ remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), |
|
138 | 162 |
currentPage: 1, |
139 | 163 |
totalPages: 3, |
140 | 164 |
late: '5', earlyLeave: '3', absence: '2', businessTrip: '1', weekendWork: '0' , |
... | ... | @@ -155,13 +179,7 @@ |
155 | 179 |
dayCount: 1, |
156 | 180 |
reason: "", // 사유 |
157 | 181 |
listData: [ |
158 |
- { |
|
159 |
- type: '연차', |
|
160 |
- content: '결재', |
|
161 |
- }, { |
|
162 |
- type: '반차', |
|
163 |
- content: '전결', |
|
164 |
- }], |
|
182 |
+], |
|
165 | 183 |
}; |
166 | 184 |
}, |
167 | 185 |
components:{ |
... | ... | @@ -175,23 +193,26 @@ |
175 | 193 |
}, |
176 | 194 |
}, |
177 | 195 |
methods: { |
178 |
- // 폼 검증 메서드 |
|
179 |
- validateForm() { |
|
180 |
- // 필수 입력 필드 체크 |
|
181 |
- if ( |
|
182 |
- this.category && |
|
183 |
- this.startDate && |
|
184 |
- this.startTime && |
|
185 |
- this.endDate && |
|
186 |
- this.endTime && |
|
187 |
- this.dayCount > 0 && |
|
188 |
- this.reason.trim() !== "" |
|
189 |
- ) { |
|
190 |
- this.isFormValid = true; |
|
191 |
- } else { |
|
192 |
- this.isFormValid = false; |
|
193 |
- } |
|
194 |
- }, |
|
196 |
+ handleStartClick() { |
|
197 |
+ const now = new Date(); |
|
198 |
+ const date = now.toLocaleDateString(); // 예: 2025. 5. 14. |
|
199 |
+ const time = now.toLocaleTimeString(); // 예: 오전 9:30:01 |
|
200 |
+ |
|
201 |
+ this.listData.push({ |
|
202 |
+ type: '출근', |
|
203 |
+ content: `${date} ${time}`, |
|
204 |
+ }); |
|
205 |
+ }, |
|
206 |
+ handleEndClick() { |
|
207 |
+ const now = new Date(); |
|
208 |
+ const date = now.toLocaleDateString(); // 예: 2025. 5. 14. |
|
209 |
+ const time = now.toLocaleTimeString(); // 예: 오전 9:30:01 |
|
210 |
+ |
|
211 |
+ this.listData.push({ |
|
212 |
+ type: '퇴근', |
|
213 |
+ content: `${date} ${time}`, |
|
214 |
+ }); |
|
215 |
+ }, |
|
195 | 216 |
calculateDayCount() { |
196 | 217 |
const start = new Date(`${this.startDate}T${this.startTime}:00`); |
197 | 218 |
const end = new Date(`${this.endDate}T${this.endTime}:00`); |
... | ... | @@ -217,32 +238,10 @@ |
217 | 238 |
} |
218 | 239 |
} |
219 | 240 |
|
220 |
- this.validateForm(); // dayCount 변경 후 폼 재검증 |
|
221 | 241 |
}, |
222 |
- handleSubmit() { |
|
223 |
- this.validateForm(); // 제출 시 유효성 확인 |
|
224 |
- if (this.isFormValid) { |
|
225 |
- localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
226 |
- alert("승인 요청이 완료되었습니다."); |
|
227 |
- // 추가 처리 로직 (API 요청 등) |
|
228 |
- } else { |
|
229 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
230 |
- } |
|
231 |
- }, |
|
232 | 242 |
|
233 | 243 |
|
234 | 244 |
}, |
235 |
- mounted() { |
|
236 |
- // Load the saved form data when the page is loaded |
|
237 |
- this.loadFormData(); |
|
238 |
- }, |
|
239 |
- watch: { |
|
240 |
- startDate: 'calculateDayCount', |
|
241 |
- startTime: 'calculateDayCount', |
|
242 |
- endDate: 'calculateDayCount', |
|
243 |
- endTime: 'calculateDayCount', |
|
244 |
- reason: "validateForm", |
|
245 |
- category: 'category', |
|
246 |
- }, |
|
245 |
+ |
|
247 | 246 |
}; |
248 | 247 |
</script> |
+++ client/views/pages/Manager/financial/ChuljangCostList.vue
... | ... | @@ -0,0 +1,192 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">출장비 현황</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <input type="date" class="form-control">~ <input type="date" class="form-control"> | |
9 | + <select name="" id="" class="form-select"> | |
10 | + <option value="">전체</option> | |
11 | + <option value="">미지급</option> | |
12 | + <option value="">지급</option> | |
13 | + </select> | |
14 | + <select name="" id="" class="form-select"> | |
15 | + <option value="">전체</option> | |
16 | + <option value="">출장목적</option> | |
17 | + <option value="">이름</option> | |
18 | + </select> | |
19 | + <div class="sch-input"> | |
20 | + <input type="text" class="form-control"> | |
21 | + <button class="ico-sch"><SearchOutlined /></button> | |
22 | + </div> | |
23 | + </div> | |
24 | + </div> | |
25 | + | |
26 | + <!-- Table --> | |
27 | + <div class="tbl-wrap"> | |
28 | + <table id="myTable" class="tbl data"> | |
29 | + <!-- 동적으로 <th> 생성 --> | |
30 | + <thead> | |
31 | + <tr> | |
32 | + <th>부서 </th> | |
33 | + <th>이름</th> | |
34 | + <th>출장지</th> | |
35 | + <th>출장목적</th> | |
36 | + <th>기간</th> | |
37 | + <th>구분(사용처)</th> | |
38 | + <th>금액(사용금액)</th> | |
39 | + <th>지급여부</th> | |
40 | + </tr> | |
41 | + </thead> | |
42 | + <!-- 동적으로 <td> 생성 --> | |
43 | + <tbody> | |
44 | + <tr v-for="(item, index) in listData" :key="index"> | |
45 | + <td>{{ item.department }}</td> | |
46 | + <td>{{ item.name }}</td> | |
47 | + <td>{{ item.location }}</td> | |
48 | + <td>{{ item.purpose }}</td> | |
49 | + <td>{{ item.period }}</td> | |
50 | + <td>{{ item.category }}</td> | |
51 | + <td>{{ formatCurrency(item.amount) }}</td> | |
52 | + <td :class="getStatusClass(item.status)">{{ item.status }}</td> | |
53 | + </tr> | |
54 | + </tbody> | |
55 | + </table> | |
56 | + | |
57 | + </div> | |
58 | + <div class="pagination"> | |
59 | + <ul> | |
60 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
61 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
62 | + < | |
63 | + </li> | |
64 | + | |
65 | + <!-- 페이지 번호 --> | |
66 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
67 | + @click="changePage(page)"> | |
68 | + {{ page }} | |
69 | + </li> | |
70 | + | |
71 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
72 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
73 | + > | |
74 | + </li> | |
75 | + </ul> | |
76 | + </div> | |
77 | + <!-- End Table --> | |
78 | + </div> | |
79 | + </div> | |
80 | + </div> | |
81 | +</template> | |
82 | + | |
83 | +<script> | |
84 | +import { ref } from 'vue'; | |
85 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
86 | +export default { | |
87 | + data() { | |
88 | + return { | |
89 | + showOptions: false, | |
90 | + currentPage: 1, | |
91 | + totalPages: 3, | |
92 | + photoicon: "/client/resources/img/photo_icon.png", | |
93 | + // 데이터 초기화 | |
94 | + years: [2023, 2024, 2025], // 연도 목록 | |
95 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
96 | + selectedYear: '', | |
97 | + selectedMonth: '', | |
98 | + listData: [ | |
99 | + { | |
100 | + department: '기획팀', | |
101 | + name: '홍길동', | |
102 | + location: '부산', | |
103 | + purpose: '고객 미팅', | |
104 | + period: '2025-05-10 ~ 2025-05-12', | |
105 | + category: '교통(기차)', | |
106 | + amount: 120000, | |
107 | + status: '지급' | |
108 | + }, | |
109 | + { | |
110 | + department: '기획팀', | |
111 | + name: '홍길동', | |
112 | + location: '부산', | |
113 | + purpose: '고객 미팅', | |
114 | + period: '2025-05-10 ~ 2025-05-12', | |
115 | + category: '교통(기차)', | |
116 | + amount: 120000, | |
117 | + status: '미지급' | |
118 | + }, | |
119 | + // ... 다른 항목들 | |
120 | + ], | |
121 | + filteredData: [], | |
122 | + }; | |
123 | + }, | |
124 | + components:{ | |
125 | + SearchOutlined | |
126 | + }, | |
127 | + computed: { | |
128 | + }, | |
129 | + methods: { | |
130 | + goToDetailPage(item) { | |
131 | + // item.id 또는 다른 식별자를 사용하여 URL을 구성할 수 있습니다. | |
132 | + this.$router.push({ path: `/employeeSalaryDetail.page`, query: { id: item.id } }); | |
133 | + }, | |
134 | + formatCurrency(amount) { | |
135 | + return new Intl.NumberFormat('ko-KR').format(amount) + ' 원'; | |
136 | + }, | |
137 | + viewPayslip(item) { | |
138 | + // 예: 모달 열기, PDF 보기, 페이지 이동 등 | |
139 | + console.log('명세서 보기:', item); | |
140 | + // window.open(item.slipUrl, '_blank'); // 외부 링크 열기 예시 | |
141 | + }, | |
142 | + changePage(page) { | |
143 | + if (page < 1 || page > this.totalPages) return; | |
144 | + this.currentPage = page; | |
145 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
146 | + }, | |
147 | + async onClickSubmit() { | |
148 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
149 | + const { mutate, onDone, onError } = useMutation(mygql); | |
150 | + | |
151 | + try { | |
152 | + const result = await mutate(); | |
153 | + console.log(result); | |
154 | + } catch (error) { | |
155 | + console.error('Mutation error:', error); | |
156 | + } | |
157 | + }, | |
158 | + goToPage(type) { | |
159 | + if (type === '휴가') { | |
160 | + this.$router.push('/HyugaInsert.page'); | |
161 | + } else if (type === '출장') { | |
162 | + this.$router.push('/ChuljangDetail.page'); | |
163 | + } | |
164 | + }, | |
165 | + getStatusClass(status) { | |
166 | + if (status === '지급') return 'status-approved'; | |
167 | + if (status === '미지급') return 'status-pending'; | |
168 | + return ''; | |
169 | + }, | |
170 | + isPastPeriod(period) { | |
171 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
172 | + const endDateStr = period.split('~')[1]?.trim(); | |
173 | + if (!endDateStr) return false; | |
174 | + | |
175 | + const endDate = new Date(endDateStr); | |
176 | + const today = new Date(); | |
177 | + | |
178 | + // 현재 날짜보다 과거면 true | |
179 | + return endDate < today; | |
180 | + } | |
181 | + }, | |
182 | + created() { | |
183 | + }, | |
184 | + mounted() { | |
185 | + | |
186 | + | |
187 | + }, | |
188 | +}; | |
189 | +</script> | |
190 | + | |
191 | +<style scoped> | |
192 | +</style> |
+++ client/views/pages/Manager/financial/MeetingCostList.vue
... | ... | @@ -0,0 +1,165 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">회의비 지출 현황</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <input type="date" class="form-control">~ <input type="date" class="form-control"> | |
9 | + <select name="" id="" class="form-select"> | |
10 | + <option value="">전체</option> | |
11 | + <option value="">프로젝트명</option> | |
12 | + <option value="">회의명</option> | |
13 | + </select> | |
14 | + <div class="sch-input"> | |
15 | + <input type="text" class="form-control"> | |
16 | + <button class="ico-sch"><SearchOutlined /></button> | |
17 | + </div> | |
18 | + </div> | |
19 | + </div> | |
20 | + | |
21 | + <!-- Table --> | |
22 | + <div class="tbl-wrap"> | |
23 | + <table id="myTable" class="tbl data"> | |
24 | + <!-- 동적으로 <th> 생성 --> | |
25 | + <thead> | |
26 | + <tr> | |
27 | + <th>프로젝트명 </th> | |
28 | + <th>회의명</th> | |
29 | + <th>회의비</th> | |
30 | + <th>기간</th> | |
31 | + </tr> | |
32 | + </thead> | |
33 | + <!-- 동적으로 <td> 생성 --> | |
34 | + <tbody> | |
35 | + <tr v-for="(item, index) in listData" :key="index"> | |
36 | + <td>{{ item.projectName }}</td> | |
37 | + <td>{{ item.meetingName }}</td> | |
38 | + <td>{{ formatCurrency(item.meetingCost) }}</td> | |
39 | + <td>{{ item.period }}</td> | |
40 | + </tr> | |
41 | + </tbody> | |
42 | + </table> | |
43 | + | |
44 | + </div> | |
45 | + <div class="pagination"> | |
46 | + <ul> | |
47 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
48 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @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 }" @click="changePage(currentPage + 1)"> | |
60 | + > | |
61 | + </li> | |
62 | + </ul> | |
63 | + </div> | |
64 | + <!-- End Table --> | |
65 | + </div> | |
66 | + </div> | |
67 | + </div> | |
68 | +</template> | |
69 | + | |
70 | +<script> | |
71 | +import { ref } from 'vue'; | |
72 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
73 | +export default { | |
74 | + data() { | |
75 | + return { | |
76 | + showOptions: false, | |
77 | + currentPage: 1, | |
78 | + totalPages: 3, | |
79 | + photoicon: "/client/resources/img/photo_icon.png", | |
80 | + // 데이터 초기화 | |
81 | + years: [2023, 2024, 2025], // 연도 목록 | |
82 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
83 | + selectedYear: '', | |
84 | + selectedMonth: '', | |
85 | + listData: [ | |
86 | + { | |
87 | + projectName: 'AI 시스템 구축', | |
88 | + meetingName: '기획 회의', | |
89 | + meetingCost: 50000, | |
90 | + period: '2025-05-10 ~ 2025-05-10' | |
91 | + }, | |
92 | + // ... 추가 항목 | |
93 | + ], | |
94 | + filteredData: [], | |
95 | + }; | |
96 | + }, | |
97 | + components:{ | |
98 | + SearchOutlined | |
99 | + }, | |
100 | + computed: { | |
101 | + }, | |
102 | + methods: { | |
103 | + goToDetailPage(item) { | |
104 | + // item.id 또는 다른 식별자를 사용하여 URL을 구성할 수 있습니다. | |
105 | + this.$router.push({ path: `/employeeSalaryDetail.page`, query: { id: item.id } }); | |
106 | + }, | |
107 | + formatCurrency(amount) { | |
108 | + return new Intl.NumberFormat('ko-KR').format(amount) + ' 원'; | |
109 | + }, | |
110 | + viewPayslip(item) { | |
111 | + // 예: 모달 열기, PDF 보기, 페이지 이동 등 | |
112 | + console.log('명세서 보기:', item); | |
113 | + // window.open(item.slipUrl, '_blank'); // 외부 링크 열기 예시 | |
114 | + }, | |
115 | + changePage(page) { | |
116 | + if (page < 1 || page > this.totalPages) return; | |
117 | + this.currentPage = page; | |
118 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
119 | + }, | |
120 | + async onClickSubmit() { | |
121 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
122 | + const { mutate, onDone, onError } = useMutation(mygql); | |
123 | + | |
124 | + try { | |
125 | + const result = await mutate(); | |
126 | + console.log(result); | |
127 | + } catch (error) { | |
128 | + console.error('Mutation error:', error); | |
129 | + } | |
130 | + }, | |
131 | + goToPage(type) { | |
132 | + if (type === '휴가') { | |
133 | + this.$router.push('/HyugaInsert.page'); | |
134 | + } else if (type === '출장') { | |
135 | + this.$router.push('/ChuljangDetail.page'); | |
136 | + } | |
137 | + }, | |
138 | + getStatusClass(status) { | |
139 | + if (status === '지급') return 'status-approved'; | |
140 | + if (status === '미지급') return 'status-pending'; | |
141 | + return ''; | |
142 | + }, | |
143 | + isPastPeriod(period) { | |
144 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
145 | + const endDateStr = period.split('~')[1]?.trim(); | |
146 | + if (!endDateStr) return false; | |
147 | + | |
148 | + const endDate = new Date(endDateStr); | |
149 | + const today = new Date(); | |
150 | + | |
151 | + // 현재 날짜보다 과거면 true | |
152 | + return endDate < today; | |
153 | + } | |
154 | + }, | |
155 | + created() { | |
156 | + }, | |
157 | + mounted() { | |
158 | + | |
159 | + | |
160 | + }, | |
161 | +}; | |
162 | +</script> | |
163 | + | |
164 | +<style scoped> | |
165 | +</style> |
+++ client/views/pages/Manager/financial/employeeSalaryDetail.vue
... | ... | @@ -0,0 +1,273 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body"> | |
4 | + <h2 class="card-title">직원별 급여명세서</h2> | |
5 | + <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" | |
6 | + @submit.prevent="handleRegister" novalidate> | |
7 | + <div class="col-12"> | |
8 | + <label for="yourName" class="form-label">아이디</label> | |
9 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
10 | + placeholder="admin"> | |
11 | + </div> | |
12 | + <div class="col-12 "> | |
13 | + <div class="col-12 border-x"> | |
14 | + <label for="youremail" class="form-label ">이름<p class="require"><img :src="require" alt=""></p></label> | |
15 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly> | |
16 | + </div> | |
17 | + | |
18 | + <div class="col-12 border-x"> | |
19 | + <label for="yourPassword" class="form-label">부서</label> | |
20 | + <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" | |
21 | + required readonly > | |
22 | + </div> | |
23 | + </div> | |
24 | + <div class="col-12"> | |
25 | + <div class="col-12 border-x"> | |
26 | + <label for="youremail" class="form-label">직급</label> | |
27 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly | |
28 | + > | |
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" id="yourPassword" | |
34 | + required readonly> | |
35 | + </div> | |
36 | + </div> | |
37 | + <div class="col-12 border-x"> | |
38 | + <div class="col-12 border-x"> | |
39 | + <label for="youremail" class="form-label">월</label> | |
40 | + <select name="" id="" class="form-select"> | |
41 | + <option value="">월</option> | |
42 | + </select> | |
43 | + </div> | |
44 | + | |
45 | + <div class="col-12 border-x"> | |
46 | + <label for="yourPassword" class="form-label">지급일</label> | |
47 | + <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" | |
48 | + required readonly> | |
49 | + </div> | |
50 | + </div> | |
51 | + | |
52 | + | |
53 | + </form> | |
54 | + | |
55 | + <div class="tbl-wrap tbl2" style="margin-top: 3rem;"> | |
56 | + <table id="myTable" class="tbl data"> | |
57 | + <!-- 동적으로 <th> 생성 --> | |
58 | + <thead> | |
59 | + <tr class="toptitle"><th colspan="5" >세부내역</th></tr> | |
60 | + <tr class="middletitle"> | |
61 | + <th colspan="3">지급</th> | |
62 | + <th colspan="2">공제</th> | |
63 | + </tr> | |
64 | + </thead> | |
65 | + <!-- 동적으로 <td> 생성 --> | |
66 | + <tbody> | |
67 | + <tr> | |
68 | + <th rowspan="7">매월 지급 </th> | |
69 | + <th>임금항목</th> | |
70 | + <th>지급 금액(원)</th> | |
71 | + <th>공제 항목</th> | |
72 | + <th>공제 금액(원)</th> | |
73 | + </tr> | |
74 | + <tr > | |
75 | + <td></td> | |
76 | + <td></td> | |
77 | + <td></td> | |
78 | + <td></td> | |
79 | + </tr> | |
80 | + <tr > | |
81 | + <td></td> | |
82 | + <td></td> | |
83 | + <td></td> | |
84 | + <td></td> | |
85 | + </tr> | |
86 | + <tr > | |
87 | + <td></td> | |
88 | + <td></td> | |
89 | + <td></td> | |
90 | + <td></td> | |
91 | + </tr> | |
92 | + <tr > | |
93 | + <td></td> | |
94 | + <td></td> | |
95 | + <td></td> | |
96 | + <td></td> | |
97 | + </tr> | |
98 | + <tr > | |
99 | + <td></td> | |
100 | + <td></td> | |
101 | + <td></td> | |
102 | + <td></td> | |
103 | + </tr> | |
104 | + | |
105 | + </tbody> | |
106 | + <tbody> | |
107 | + <tr> | |
108 | + <th rowspan="3">격월 또는 부정기 지급</th> | |
109 | + <td></td> | |
110 | + <td></td> | |
111 | + <td></td> | |
112 | + <td></td> | |
113 | + </tr> | |
114 | + <tr> | |
115 | + <td></td> | |
116 | + <td></td> | |
117 | + <td></td> | |
118 | + <td></td> | |
119 | + </tr> | |
120 | + <tr> | |
121 | + <td></td> | |
122 | + <td></td> | |
123 | + <td></td> | |
124 | + <td></td> | |
125 | + </tr> | |
126 | + <tr> | |
127 | + <th colspan="2">지급액 계</th> | |
128 | + <td></td> | |
129 | + <th >지금액 계</th> | |
130 | + <td></td> | |
131 | + </tr> | |
132 | + <tr> | |
133 | + <td colspan="3"></td> | |
134 | + <th> 실수령액(원)</th> | |
135 | + <td></td> | |
136 | + </tr> | |
137 | + </tbody> | |
138 | + </table> | |
139 | + <table id="myTable" class="tbl data" style="margin-top: 10px;"> | |
140 | + <!-- 동적으로 <th> 생성 --> | |
141 | + <thead> | |
142 | + <tr class="toptitle"><th colspan="3" >세부내역</th></tr> | |
143 | + <tr > | |
144 | + <th >구분</th> | |
145 | + <th>산출식 또는 산출방법</th> | |
146 | + <th>지급액(원)</th> | |
147 | + </tr> | |
148 | + </thead> | |
149 | + <!-- 동적으로 <td> 생성 --> | |
150 | + <tbody> | |
151 | + <tr > | |
152 | + <td></td> | |
153 | + <td></td> | |
154 | + <td></td> | |
155 | + </tr> | |
156 | + | |
157 | + | |
158 | + </tbody> | |
159 | + | |
160 | + </table> | |
161 | + | |
162 | + | |
163 | + </div> | |
164 | + <div class="buttons"> | |
165 | + <button type="submit" class="btn tertiary">목록</button> | |
166 | + </div> | |
167 | + </div> | |
168 | + | |
169 | +</div> | |
170 | + | |
171 | +</template> | |
172 | + | |
173 | +<script> | |
174 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
175 | + | |
176 | +export default { | |
177 | + data() { | |
178 | + return { | |
179 | + photoicon: "/client/resources/img/photo_icon.png", | |
180 | + img1: "/client/resources/img/img.png", | |
181 | + icon1: "/client/resources/img/icon.png", | |
182 | + dateicon: "/client/resources/img/date.png", | |
183 | + startbtn: "/client/resources/img/start.png", | |
184 | + stopbtn: "/client/resources/img/stop.png", | |
185 | + moreicon: "/client/resources/img/more.png", | |
186 | + today: new Date().toLocaleDateString('ko-KR', { | |
187 | + year: 'numeric', | |
188 | + month: '2-digit', | |
189 | + day: '2-digit', | |
190 | + weekday: 'short', | |
191 | + }), | |
192 | + time: this.getCurrentTime(), | |
193 | + listData: [ | |
194 | + { | |
195 | + type: '내부', | |
196 | + projectName: 'AI 개발 프로젝트', | |
197 | + pm: '홍길동', | |
198 | + budget: 50000000, | |
199 | + period: '2024-01-01 ~ 2024-12-31', | |
200 | + status: '진행중' | |
201 | + }, | |
202 | + { | |
203 | + type: '외부', | |
204 | + projectName: '웹 리뉴얼', | |
205 | + pm: '김영희', | |
206 | + budget: 20000000, | |
207 | + period: '2023-01-01 ~ 2023-12-31', | |
208 | + status: '미진행' | |
209 | + } | |
210 | + ] | |
211 | + } | |
212 | + }, | |
213 | + methods: { | |
214 | + formatBudget(amount) { | |
215 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
216 | + }, | |
217 | + isPastPeriod(period) { | |
218 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
219 | + const endDateStr = period.split('~')[1]?.trim(); | |
220 | + if (!endDateStr) return false; | |
221 | + | |
222 | + const endDate = new Date(endDateStr); | |
223 | + const today = new Date(); | |
224 | + | |
225 | + // 현재 날짜보다 과거면 true | |
226 | + return endDate < today; | |
227 | + }, | |
228 | + getStatusClass(status) { | |
229 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
230 | + }, | |
231 | + getStatusClass(status) { | |
232 | + if (status === '미진행') return 'status-pending'; | |
233 | + if (status === '진행중') return 'status-approved'; | |
234 | + | |
235 | + // Default empty string | |
236 | + return ''; | |
237 | +}, | |
238 | + getCurrentTime() { | |
239 | + const now = new Date(); | |
240 | + const hours = String(now.getHours()).padStart(2, '0'); | |
241 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
242 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
243 | + return `${hours}:${minutes}:${seconds}`; | |
244 | + }, | |
245 | + getCategoryClass(category) { | |
246 | + switch (category) { | |
247 | + case '용역': return 'category-service'; | |
248 | + case '내부': return 'category-internal'; | |
249 | + case '국가과제': return 'category-government'; | |
250 | + default: return ''; | |
251 | + } | |
252 | + }, | |
253 | + }, | |
254 | + watch: { | |
255 | + | |
256 | + }, | |
257 | + computed: { | |
258 | + | |
259 | + }, | |
260 | + components: { | |
261 | + GoogleCalendar, | |
262 | + }, | |
263 | + mounted() { | |
264 | + console.log('main mounted'); | |
265 | + setInterval(() => { | |
266 | + this.time = this.getCurrentTime(); | |
267 | + }, 1000); | |
268 | + } | |
269 | +} | |
270 | +</script> | |
271 | +<style scoped> | |
272 | +tr{cursor: pointer;} | |
273 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/financial/employeeSalaryInsert.vue
... | ... | @@ -0,0 +1,438 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body"> | |
4 | + <h2 class="card-title">급여명세서 등록</h2> | |
5 | + <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" | |
6 | + @submit.prevent="handleRegister" novalidate > | |
7 | + <div class="col-12"> | |
8 | + <label for="yourName" class="form-label"><p>아이디<p class="require"><img :src="require" alt=""></p></p> </label> | |
9 | + <input v-model="selectedid" type="text" name="name" class="form-control" required readonly | |
10 | + > | |
11 | + <input type="button" class="form-control " value="검색" @click="showPopup1 = true" /> | |
12 | + <HrPopup v-if="showPopup1" @close="showPopup1 = false" @select="addApproval" /> | |
13 | + </div> | |
14 | + <div class="col-12 "> | |
15 | + <div class="col-12 border-x"> | |
16 | + <label for="youremail" class="form-label ">이름</label> | |
17 | + <input v-model="selectedname" type="text" class="form-control" required readonly> | |
18 | + </div> | |
19 | + | |
20 | + <div class="col-12 border-x"> | |
21 | + <label for="yourPassword" class="form-label">부서</label> | |
22 | + <input v-model="selecteddepartment" type="text" class="form-control" | |
23 | + required readonly > | |
24 | + </div> | |
25 | + </div> | |
26 | + <div class="col-12"> | |
27 | + <div class="col-12 border-x"> | |
28 | + <label for="youremail" class="form-label">직급</label> | |
29 | + <input v-model="selectedposition" type="text" class="form-control" required readonly | |
30 | + > | |
31 | + </div> | |
32 | + | |
33 | + <div class="col-12 border-x"> | |
34 | + <label for="yourPassword" class="form-label">직책</label> | |
35 | + <input v-model="selectedrole" type="text" class="form-control" | |
36 | + required readonly> | |
37 | + </div> | |
38 | + </div> | |
39 | + <div class="col-12 border-x"> | |
40 | + <div class="col-12 border-x"> | |
41 | + <label for="youremail" class="form-label"><p>월<p class="require"><img :src="require" alt=""></p></p> </label> | |
42 | + <select name="" id="" class="form-select"> | |
43 | + <option value="">월</option> | |
44 | + </select> | |
45 | + </div> | |
46 | + | |
47 | + <div class="col-12 border-x"> | |
48 | + <label for="yourPassword" class="form-label"><p>지급일<p class="require"><img :src="require" alt=""></p></p> </label> | |
49 | + <input type="date" class="form-control" | |
50 | + required > | |
51 | + </div> | |
52 | + </div> | |
53 | + | |
54 | + | |
55 | + </form> | |
56 | + <div class="buttons" style="margin: 10px 0;"> | |
57 | + <button type="submit" class="btn btn-red sm">지난달 정보 불러오기</button> | |
58 | + <button type="submit" class="btn primary sm" @click="showPopup = true">출장비 조회</button> | |
59 | + <ChuljangListPopup v-if="showPopup" @close="showPopup = false" @select="addApproval" /> | |
60 | + </div> | |
61 | + <form class="row g-3 needs-validation" @submit.prevent="handleSubmit"> | |
62 | + <div class="col-12 form-title">매월지급</div> | |
63 | + <div class="col-12"> | |
64 | + <label for="purpose" class="form-label"><p>지급 <button type="button" title="추가" @click="addPayment"> | |
65 | + <PlusCircleFilled /> | |
66 | + </button><p class="require"><img :src="require" alt=""></p></p> </label> | |
67 | + <div class="approval-container"> | |
68 | + <div v-for="(payment, index) in payments" :key="index" class="d-flex gap-2 addapproval mb-2"> | |
69 | + <select class="form-select" style="width: 200px;" v-model="payment.category"> | |
70 | + <option value="기본급">기본급</option> | |
71 | + <option value=""></option> | |
72 | + <option value=""></option> | |
73 | + </select> | |
74 | + <input type="text" class="form-control" v-model="payment.pay" /> | |
75 | + <button type="button" @click="removePayment(index)" class="delete-button"> | |
76 | + <CloseOutlined /> | |
77 | + </button> | |
78 | + </div> | |
79 | + </div> | |
80 | + </div> | |
81 | + <div class="col-12 border-x"> | |
82 | + <label for="purpose" class="form-label"><p>공제 <button type="button" title="추가" @click="addGongje"> | |
83 | + <PlusCircleFilled /> | |
84 | + </button><p class="require"><img :src="require" alt=""></p></p> </label> | |
85 | + <div class="approval-container"> | |
86 | + <div class="d-flex addapproval"> | |
87 | + <select class="form-select" style="width: 200px;" > | |
88 | + <option value="국민연금">국민연금</option> | |
89 | + <option value="">건강보험</option> | |
90 | + <option value="">고용보험</option> | |
91 | + <option value="">장기요양</option> | |
92 | + <option value="">소득세</option> | |
93 | + <option value="">지방소득세</option> | |
94 | + </select> | |
95 | + <input type="text" class="form-control" /> | |
96 | + </div> | |
97 | + <div class="d-flex addapproval"> | |
98 | + <select class="form-select" style="width: 200px;" > | |
99 | + <option value="국민연금">국민연금</option> | |
100 | + <option value="">건강보험</option> | |
101 | + <option value="">고용보험</option> | |
102 | + <option value="">장기요양</option> | |
103 | + <option value="">소득세</option> | |
104 | + <option value="">지방소득세</option> | |
105 | + </select> | |
106 | + <input type="text" class="form-control" /> | |
107 | + </div> | |
108 | + <div class="d-flex addapproval"> | |
109 | + <select class="form-select" style="width: 200px;" > | |
110 | + <option value="국민연금">국민연금</option> | |
111 | + <option value="">건강보험</option> | |
112 | + <option value="">고용보험</option> | |
113 | + <option value="">장기요양</option> | |
114 | + <option value="">소득세</option> | |
115 | + <option value="">지방소득세</option> | |
116 | + </select> | |
117 | + <input type="text" class="form-control" /> | |
118 | + </div> | |
119 | + <div class="d-flex addapproval"> | |
120 | + <select class="form-select" style="width: 200px;" > | |
121 | + <option value="국민연금">국민연금</option> | |
122 | + <option value="">건강보험</option> | |
123 | + <option value="">고용보험</option> | |
124 | + <option value="">장기요양</option> | |
125 | + <option value="">소득세</option> | |
126 | + <option value="">지방소득세</option> | |
127 | + </select> | |
128 | + <input type="text" class="form-control" /> | |
129 | + </div> | |
130 | + <div class="d-flex addapproval"> | |
131 | + <select class="form-select" style="width: 200px;" > | |
132 | + <option value="국민연금">국민연금</option> | |
133 | + <option value="">건강보험</option> | |
134 | + <option value="">고용보험</option> | |
135 | + <option value="">장기요양</option> | |
136 | + <option value="">소득세</option> | |
137 | + <option value="">지방소득세</option> | |
138 | + </select> | |
139 | + <input type="text" class="form-control" /> | |
140 | + </div> | |
141 | + <div class="d-flex addapproval"> | |
142 | + <select class="form-select" style="width: 200px;" > | |
143 | + <option value="국민연금">국민연금</option> | |
144 | + <option value="">건강보험</option> | |
145 | + <option value="">고용보험</option> | |
146 | + <option value="">장기요양</option> | |
147 | + <option value="">소득세</option> | |
148 | + <option value="">지방소득세</option> | |
149 | + </select> | |
150 | + <input type="text" class="form-control" /> | |
151 | + </div> | |
152 | + <div v-for="(gongje, index) in gongjes" :key="index" class="d-flex addapproval"> | |
153 | + <select class="form-select" style="width: 200px;" v-model="gongje.category"> | |
154 | + <option value="">기본급</option> | |
155 | + <option value=""></option> | |
156 | + <option value=""></option> | |
157 | + </select> | |
158 | + <input type="text" class="form-control" v-model="gongje.pay" /> | |
159 | + <button type="button" @click="removeGongje(index)" class="delete-button"> | |
160 | + <CloseOutlined /> | |
161 | + </button> | |
162 | + </div> | |
163 | + </div> | |
164 | + | |
165 | + </div> | |
166 | + </form> | |
167 | + <form class="row g-3 needs-validation" @submit.prevent="handleSubmit" style="margin-top: 10px;"> | |
168 | + <div class="col-12 form-title">격월 또는 부정기 지급</div> | |
169 | + <div class="col-12"> | |
170 | + <label for="purpose" class="form-label"><p>지급 <button type="button" title="추가" @click="addPayment2"> | |
171 | + <PlusCircleFilled /> | |
172 | + </button><p class="require"><img :src="require" alt=""></p></p> </label> | |
173 | + <div class="approval-container"> | |
174 | + <div v-for="(payment, index) in payments2" :key="index" class="d-flex gap-2 addapproval mb-2"> | |
175 | + <select class="form-select" style="width: 200px;" v-model="payment.category"> | |
176 | + <option value="선택">선택</option> | |
177 | + <option value=""></option> | |
178 | + <option value=""></option> | |
179 | + </select> | |
180 | + <input type="text" class="form-control" v-model="payment.pay" /> | |
181 | + <button type="button" @click="removePayment2(index)" class="delete-button"> | |
182 | + <CloseOutlined /> | |
183 | + </button> | |
184 | + </div> | |
185 | + </div> | |
186 | + </div> | |
187 | + <div class="col-12 border-x"> | |
188 | + <label for="purpose" class="form-label"><p>공제 <button type="button" title="추가" @click="addGongje2"> | |
189 | + <PlusCircleFilled /> | |
190 | + </button><p class="require"><img :src="require" alt=""></p></p> </label> | |
191 | + <div class="approval-container"> | |
192 | + | |
193 | + <div v-for="(gongje, index) in gongjes2" :key="index" class="d-flex addapproval"> | |
194 | + <select class="form-select" style="width: 200px;" v-model="gongje.category"> | |
195 | + <option value="선택">선택</option> | |
196 | + <option value=""></option> | |
197 | + <option value=""></option> | |
198 | + </select> | |
199 | + <input type="text" class="form-control" v-model="gongje.pay" /> | |
200 | + <button type="button" @click="removeGongje2(index)" class="delete-button"> | |
201 | + <CloseOutlined /> | |
202 | + </button> | |
203 | + </div> | |
204 | + </div> | |
205 | + | |
206 | + </div> | |
207 | + </form> | |
208 | + <form class="row g-3 needs-validation" @submit.prevent="handleSubmit" style="margin-top: 10px;"> | |
209 | + <div class="col-12 form-title">계산방법<button type="button" title="추가" @click="addTr"> | |
210 | + <PlusCircleFilled /> | |
211 | + </button></div> | |
212 | + <div class="col-12 border-x" > | |
213 | + <div class="tbl-wrap tbl3"> | |
214 | + <table class="tbl"> | |
215 | + <thead> | |
216 | + <tr> | |
217 | + <th>지급</th> | |
218 | + <th>산출식 또는 산출 방법</th> | |
219 | + <th>지급액(원)</th> | |
220 | + </tr> | |
221 | + </thead> | |
222 | + <tbody> | |
223 | + <tr v-for="(item, index) in listData" :key="index" > | |
224 | + <td> | |
225 | + <select class="form-select" v-model="item.payType"> | |
226 | + <option value="">선택</option> | |
227 | + <option value=""></option> | |
228 | + <option value=""></option> | |
229 | + </select> | |
230 | + </td> | |
231 | + <td><input type="text" class="form-control" v-model="item.formula" /></td> | |
232 | + <td><input type="text" class="form-control" v-model="item.amount" /></td> | |
233 | + </tr> | |
234 | + </tbody> | |
235 | + </table> | |
236 | + </div> | |
237 | + </div> | |
238 | + | |
239 | + | |
240 | + </form> | |
241 | + | |
242 | + <form class="row g-3 needs-validation" @submit.prevent="handleSubmit" style="margin-top: 10px;"> | |
243 | + | |
244 | + <div class="col-12 border-x"> | |
245 | + <label for="purpose" class="form-label">지급 </label> | |
246 | + <input type="text" class="form-control"> | |
247 | + <label for="purpose" class="form-label">지급 </label> | |
248 | + <input type="text" class="form-control"> | |
249 | + <label for="purpose" class="form-label">지급 </label> | |
250 | + <input type="text" class="form-control"> | |
251 | + </div> | |
252 | + </form> | |
253 | + <div class="buttons"> | |
254 | + <button type="submit" class="btn primary">등록</button> | |
255 | + <button type="submit" class="btn tertiary">취소</button> | |
256 | + </div> | |
257 | + </div> | |
258 | + | |
259 | +</div> | |
260 | + | |
261 | +</template> | |
262 | + | |
263 | +<script> | |
264 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
265 | +import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; | |
266 | +import HrPopup from "../../../component/Popup/HrPopup.vue"; | |
267 | +import ChuljangListPopup from "../../../component/Popup/ChuljangListPopup.vue"; | |
268 | +export default { | |
269 | + data() { | |
270 | + return { | |
271 | + showPopup: false, | |
272 | + showPopup1: false, | |
273 | + selectedid: '', | |
274 | + selectedname: '', | |
275 | + selectedposition: '', | |
276 | + selectedrole: '', | |
277 | + selecteddepartment: '', | |
278 | + approvals: [], | |
279 | + listData: [ | |
280 | + { payType: '', formula: '', amount: '' } | |
281 | + ], | |
282 | + payments: [ { | |
283 | + category: '기본급', | |
284 | + pay: '', | |
285 | + },] , | |
286 | + gongjes: [ { | |
287 | + category: '기본급', | |
288 | + pay: '', | |
289 | + },] , | |
290 | + payments2: [ { | |
291 | + category: '선택', | |
292 | + pay: '', | |
293 | + },] , | |
294 | + gongjes2: [ { | |
295 | + category: '선택', | |
296 | + pay: '', | |
297 | + },] , | |
298 | + require: "/client/resources/img/require.png", | |
299 | + photoicon: "/client/resources/img/photo_icon.png", | |
300 | + img1: "/client/resources/img/img.png", | |
301 | + icon1: "/client/resources/img/icon.png", | |
302 | + dateicon: "/client/resources/img/date.png", | |
303 | + startbtn: "/client/resources/img/start.png", | |
304 | + stopbtn: "/client/resources/img/stop.png", | |
305 | + moreicon: "/client/resources/img/more.png", | |
306 | + today: new Date().toLocaleDateString('ko-KR', { | |
307 | + year: 'numeric', | |
308 | + month: '2-digit', | |
309 | + day: '2-digit', | |
310 | + weekday: 'short', | |
311 | + }), | |
312 | + time: this.getCurrentTime(), | |
313 | + | |
314 | + } | |
315 | + }, | |
316 | + components: { | |
317 | + PlusCircleFilled, CloseOutlined, HrPopup, ChuljangListPopup | |
318 | + }, | |
319 | + methods: { | |
320 | + addApproval(selectedUser) { | |
321 | + this.approvals.push({ | |
322 | + id: selectedUser.id, | |
323 | + name: selectedUser.name, | |
324 | + department: selectedUser.department, | |
325 | + position: selectedUser.position, | |
326 | + role: selectedUser.role, | |
327 | + | |
328 | + }); | |
329 | + | |
330 | + this.selectedid = selectedUser.id; // 입력창에 표시 | |
331 | + this.selectedname = selectedUser.name; // 입력창에 표시 | |
332 | + this.selecteddepartment = selectedUser.department; // 입력창에 표시 | |
333 | + this.selectedposition = selectedUser.position; // 입력창에 표시 | |
334 | + this.selectedrole = selectedUser.role; // 입력창에 표시 | |
335 | + this.showPopup1 = false; | |
336 | + }, | |
337 | + addTr() { | |
338 | + this.listData.push({ payType: '', formula: '', amount: '' }); | |
339 | + }, | |
340 | + addPayment() { | |
341 | + this.payments.push({ | |
342 | + category: '선택', | |
343 | + pay: '', | |
344 | + }); | |
345 | + }, | |
346 | + removePayment(index) { | |
347 | + this.payments.splice(index, 1); | |
348 | + }, | |
349 | + addGongje() { | |
350 | + this.gongjes.push({ | |
351 | + category: '선택', | |
352 | + pay: '', | |
353 | + }); | |
354 | + }, | |
355 | + removeGongje(index) { | |
356 | + this.gongjes.splice(index, 1); | |
357 | + }, | |
358 | + addPayment2() { | |
359 | + this.payments2.push({ | |
360 | + category: '선택', | |
361 | + pay: '', | |
362 | + }); | |
363 | + }, | |
364 | + removePayment2(index) { | |
365 | + this.payments2.splice(index, 1); | |
366 | + }, | |
367 | + addGongje2() { | |
368 | + this.gongjes2.push({ | |
369 | + category: '선택', | |
370 | + pay: '', | |
371 | + }); | |
372 | + }, | |
373 | + removeGongje2(index) { | |
374 | + this.gongjes2.splice(index, 1); | |
375 | + }, | |
376 | + formatBudget(amount) { | |
377 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
378 | + }, | |
379 | + isPastPeriod(period) { | |
380 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
381 | + const endDateStr = period.split('~')[1]?.trim(); | |
382 | + if (!endDateStr) return false; | |
383 | + | |
384 | + const endDate = new Date(endDateStr); | |
385 | + const today = new Date(); | |
386 | + | |
387 | + // 현재 날짜보다 과거면 true | |
388 | + return endDate < today; | |
389 | + }, | |
390 | + getStatusClass(status) { | |
391 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
392 | + }, | |
393 | + getStatusClass(status) { | |
394 | + if (status === '미진행') return 'status-pending'; | |
395 | + if (status === '진행중') return 'status-approved'; | |
396 | + | |
397 | + // Default empty string | |
398 | + return ''; | |
399 | +}, | |
400 | + getCurrentTime() { | |
401 | + const now = new Date(); | |
402 | + const hours = String(now.getHours()).padStart(2, '0'); | |
403 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
404 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
405 | + return `${hours}:${minutes}:${seconds}`; | |
406 | + }, | |
407 | + getCategoryClass(category) { | |
408 | + switch (category) { | |
409 | + case '용역': return 'category-service'; | |
410 | + case '내부': return 'category-internal'; | |
411 | + case '국가과제': return 'category-government'; | |
412 | + default: return ''; | |
413 | + } | |
414 | + }, | |
415 | + }, | |
416 | + watch: { | |
417 | + | |
418 | + }, | |
419 | + computed: { | |
420 | + | |
421 | + }, | |
422 | + mounted() { | |
423 | + console.log('main mounted'); | |
424 | + setInterval(() => { | |
425 | + this.time = this.getCurrentTime(); | |
426 | + }, 1000); | |
427 | + } | |
428 | +} | |
429 | +</script> | |
430 | +<style scoped> | |
431 | +tr{cursor: pointer;} | |
432 | +.approval-container{ | |
433 | + width: 100%; | |
434 | +} | |
435 | +.addapproval input{ | |
436 | + width: calc(100% - 240px); | |
437 | +} | |
438 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/financial/employeeSalaryList.vue
... | ... | @@ -0,0 +1,190 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">직원별 급여명세서</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option :value="currentYear">{{ currentYear }}년</option> | |
10 | + <option value="all">전체</option> | |
11 | + <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> | |
12 | + {{ year }}년 | |
13 | + </option> | |
14 | + </select> | |
15 | + <select name="" id="" class="form-select"> | |
16 | + <option :value="currentMonth">{{ currentMonth }}월</option> | |
17 | + <option value="all">전체</option> | |
18 | + <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> | |
19 | + {{ month }}월 | |
20 | + </option> | |
21 | + </select> | |
22 | + <select name="" id="" class="form-select"> | |
23 | + <option value="">부서</option> | |
24 | + </select> | |
25 | + <div class="sch-input"> | |
26 | + <input type="text" class="form-control"> | |
27 | + <button class="ico-sch"><SearchOutlined /></button> | |
28 | + </div> | |
29 | + </div> | |
30 | + </div> | |
31 | + | |
32 | + <!-- Table --> | |
33 | + <div class="tbl-wrap"> | |
34 | + <table id="myTable" class="tbl data"> | |
35 | + <!-- 동적으로 <th> 생성 --> | |
36 | + <thead> | |
37 | + <tr> | |
38 | + <th>월 </th> | |
39 | + <th>부서</th> | |
40 | + <th>이름</th> | |
41 | + <th>지급액</th> | |
42 | + <th>공제액</th> | |
43 | + <th>수령액</th> | |
44 | + </tr> | |
45 | + </thead> | |
46 | + <!-- 동적으로 <td> 생성 --> | |
47 | + <tbody> | |
48 | + <tr v-for="(item, index) in listData" :key="index" @click="goToDetailPage(item)"> | |
49 | + <td>{{ item.month }}</td> | |
50 | + <td>{{ item.department }}</td> | |
51 | + <td>{{ item.name }}</td> | |
52 | + <td>{{ formatCurrency(item.payment) }}</td> | |
53 | + <td>{{ formatCurrency(item.deduction) }}</td> | |
54 | + <td>{{ formatCurrency(item.actual) }}</td> | |
55 | + </tr> | |
56 | + </tbody> | |
57 | + </table> | |
58 | + | |
59 | + </div> | |
60 | + <div class="pagination"> | |
61 | + <ul> | |
62 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
63 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
64 | + < | |
65 | + </li> | |
66 | + | |
67 | + <!-- 페이지 번호 --> | |
68 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
69 | + @click="changePage(page)"> | |
70 | + {{ page }} | |
71 | + </li> | |
72 | + | |
73 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
74 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
75 | + > | |
76 | + </li> | |
77 | + </ul> | |
78 | + </div> | |
79 | + <!-- End Table --> | |
80 | + </div> | |
81 | + </div> | |
82 | + </div> | |
83 | +</template> | |
84 | + | |
85 | +<script> | |
86 | +import { ref } from 'vue'; | |
87 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
88 | +const currentYear = new Date().getFullYear(); | |
89 | +const currentMonth = new Date().getMonth() + 1; | |
90 | +export default { | |
91 | + data() { | |
92 | + return { | |
93 | + currentMonth, | |
94 | + selectedMonth: currentMonth, | |
95 | + remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), | |
96 | + currentYear, | |
97 | + selectedYear: currentYear, | |
98 | + remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), | |
99 | + showOptions: false, | |
100 | + currentPage: 1, | |
101 | + totalPages: 3, | |
102 | + photoicon: "/client/resources/img/photo_icon.png", | |
103 | + // 데이터 초기화 | |
104 | + years: [2023, 2024, 2025], // 연도 목록 | |
105 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
106 | + selectedYear: '', | |
107 | + selectedMonth: '', | |
108 | + listData: [ | |
109 | + { | |
110 | + month: '4', | |
111 | + department: '개발팀', | |
112 | + name: '홍길동', | |
113 | + payment: 3000000, | |
114 | + deduction: 300000, | |
115 | + actual: 2700000, | |
116 | + }, | |
117 | + ], | |
118 | + filteredData: [], | |
119 | + }; | |
120 | + }, | |
121 | + components:{ | |
122 | + SearchOutlined | |
123 | + }, | |
124 | + computed: { | |
125 | + }, | |
126 | + methods: { | |
127 | + goToDetailPage(item) { | |
128 | + // item.id 또는 다른 식별자를 사용하여 URL을 구성할 수 있습니다. | |
129 | + this.$router.push({ path: `/employeeSalaryDetail.page`, query: { id: item.id } }); | |
130 | + }, | |
131 | + formatCurrency(amount) { | |
132 | + return new Intl.NumberFormat('ko-KR').format(amount) + ' 원'; | |
133 | + }, | |
134 | + viewPayslip(item) { | |
135 | + // 예: 모달 열기, PDF 보기, 페이지 이동 등 | |
136 | + console.log('명세서 보기:', item); | |
137 | + // window.open(item.slipUrl, '_blank'); // 외부 링크 열기 예시 | |
138 | + }, | |
139 | + changePage(page) { | |
140 | + if (page < 1 || page > this.totalPages) return; | |
141 | + this.currentPage = page; | |
142 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
143 | + }, | |
144 | + async onClickSubmit() { | |
145 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
146 | + const { mutate, onDone, onError } = useMutation(mygql); | |
147 | + | |
148 | + try { | |
149 | + const result = await mutate(); | |
150 | + console.log(result); | |
151 | + } catch (error) { | |
152 | + console.error('Mutation error:', error); | |
153 | + } | |
154 | + }, | |
155 | + goToPage(type) { | |
156 | + if (type === '휴가') { | |
157 | + this.$router.push('/HyugaInsert.page'); | |
158 | + } else if (type === '출장') { | |
159 | + this.$router.push('/ChuljangDetail.page'); | |
160 | + } | |
161 | + }, | |
162 | + getStatusClass(status) { | |
163 | + if (status === '승인') return 'status-approved'; | |
164 | + if (status === '대기') return 'status-pending'; | |
165 | + return ''; | |
166 | + }, | |
167 | + isPastPeriod(period) { | |
168 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
169 | + const endDateStr = period.split('~')[1]?.trim(); | |
170 | + if (!endDateStr) return false; | |
171 | + | |
172 | + const endDate = new Date(endDateStr); | |
173 | + const today = new Date(); | |
174 | + | |
175 | + // 현재 날짜보다 과거면 true | |
176 | + return endDate < today; | |
177 | + } | |
178 | + }, | |
179 | + created() { | |
180 | + }, | |
181 | + mounted() { | |
182 | + | |
183 | + | |
184 | + }, | |
185 | +}; | |
186 | +</script> | |
187 | + | |
188 | +<style scoped> | |
189 | +tr{cursor: pointer;} | |
190 | +</style> |
--- client/views/pages/Manager/financial/financial.vue
+++ client/views/pages/Manager/financial/financial.vue
... | ... | @@ -1,203 +1,121 @@ |
1 | 1 |
<template> |
2 |
- <div class="pagetitle"> |
|
3 |
- <h2>공지사항 등록</h2> |
|
4 |
- </div> |
|
5 |
- <section class="section"> |
|
6 |
- <div class="card"> |
|
7 |
- <div class="card-body pt-3"> |
|
8 |
- <form @submit.prevent="handleSubmit"> |
|
9 |
- <table class="form-table" style="width: 100%;"> |
|
10 |
- <tbody> |
|
11 |
- <tr> |
|
12 |
- <th class="text-lf"> |
|
13 |
- <span>제목</span> |
|
14 |
- </th> |
|
15 |
- <td> |
|
16 |
- <input type="text" class="form-control" placeholder="제목을 입력하세요." v-model="title" required /> |
|
17 |
- </td> |
|
18 |
- </tr> |
|
19 |
- <tr class="border-top"> |
|
20 |
- <th colspan="4" class="text-lf"> |
|
21 |
- <span>내용</span> |
|
22 |
- </th> |
|
23 |
- </tr> |
|
24 |
- <tr style="max-height: 600px"> |
|
25 |
- <td colspan="4" style="height: 100%" class="pt-0"> |
|
26 |
- <textarea name="" id="" v-model="editorData"></textarea> |
|
27 |
- </td> |
|
28 |
- </tr> |
|
29 |
- |
|
30 |
- <!-- 첨부파일 --> |
|
31 |
- <tr class="border-top"> |
|
32 |
- <th class="text-lf"> |
|
33 |
- 첨부파일 |
|
34 |
- </th> |
|
35 |
- <td colspan="2"> |
|
36 |
- <div class="gd-12 pr0"> |
|
37 |
- <div class="gd-2 pl0 pr0"> |
|
38 |
- <label for="file" class="btn btn-outline-primary">파일찾기</label> |
|
39 |
- <input type="file" id="file" ref="file" multiple /> |
|
40 |
- </div> |
|
41 |
- </div> |
|
42 |
- </td> |
|
43 |
- </tr> |
|
44 |
- |
|
45 |
- <!-- 공지글 --> |
|
46 |
- <tr class="border-top"> |
|
47 |
- <th class="text-lf"> |
|
48 |
- 공지글 |
|
49 |
- </th> |
|
50 |
- <td colspan="3"> |
|
51 |
- <div class="d-flex no-gutters"> |
|
52 |
- <div class="col-md-4"> |
|
53 |
- <input type="radio" name="notice" id="notice-y" class="mr5" value="Y" v-model="notice" required /> |
|
54 |
- <label for="notice-y">사용</label> |
|
55 |
- </div> |
|
56 |
- <div class="col-md-4"> |
|
57 |
- <input type="radio" name="notice" id="notice-n" class="mr5" value="N" v-model="notice" required /> |
|
58 |
- <label for="notice-n">미사용</label> |
|
59 |
- </div> |
|
60 |
- </div> |
|
61 |
- </td> |
|
62 |
- </tr> |
|
63 |
- |
|
64 |
- <!-- 공지글 게시기간 --> |
|
65 |
- <tr class="border-top"> |
|
66 |
- <th class="text-lf"> |
|
67 |
- 공지글 게시기간 |
|
68 |
- </th> |
|
69 |
- <td colspan="3"> |
|
70 |
- <div class="d-flex no-gutters"> |
|
71 |
- <div class="col-md-4"> |
|
72 |
- <input type="datetime-local" class="form-control" v-model="startDate" :disabled="notice === 'N'" |
|
73 |
- required /> |
|
74 |
- </div> |
|
75 |
- <div class="pd-1">-</div> |
|
76 |
- <div class="col-md-4"> |
|
77 |
- <input type="datetime-local" class="form-control" v-model="endDate" :disabled="notice === 'N'" |
|
78 |
- required /> |
|
79 |
- </div> |
|
80 |
- </div> |
|
81 |
- </td> |
|
82 |
- </tr> |
|
83 |
- </tbody> |
|
84 |
- </table> |
|
85 |
- <div class="text-end"> |
|
86 |
- <button class="btn btn-primary" type="submit">등록</button> |
|
87 |
- <button class="btn btn-secondary" @click="handleCancel">취소</button> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo simple"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
88 | 8 |
</div> |
89 |
- </form> |
|
90 |
- |
|
91 |
- |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
13 |
+ </div> |
|
14 |
+ </div> |
|
92 | 15 |
</div> |
16 |
+ |
|
17 |
+ |
|
18 |
+ <details class="menu-box"> |
|
19 |
+ <summary><p>급여관리</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
20 |
+ <ul> |
|
21 |
+ <li> <router-link to="/salaryList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
22 |
+ <p>급여명세서</p> |
|
23 |
+ <div class="icon" v-if="isExactActive"> |
|
24 |
+ <img :src="menuicon" alt=""> |
|
25 |
+ </div> |
|
26 |
+ </router-link></li> |
|
27 |
+ <li> |
|
28 |
+ <router-link to="/employeeSalaryList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
29 |
+ <p>직원별 급여명세서</p> |
|
30 |
+ <div class="icon" v-if="isExactActive"> |
|
31 |
+ <img :src="menuicon" alt=""> |
|
32 |
+ </div> |
|
33 |
+ </router-link> |
|
34 |
+ </li> |
|
35 |
+ <li> |
|
36 |
+ <router-link to="/employeeSalaryInsert.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
37 |
+ <p>급여명세서 등록</p> |
|
38 |
+ <div class="icon" v-if="isExactActive"> |
|
39 |
+ <img :src="menuicon" alt=""> |
|
40 |
+ </div> |
|
41 |
+ </router-link> |
|
42 |
+ </li> |
|
43 |
+ |
|
44 |
+ </ul> |
|
45 |
+ </details> |
|
46 |
+ <details class="menu-box"> |
|
47 |
+ <summary><p>지출관리</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
48 |
+ <ul> |
|
49 |
+ <li> <router-link to="/ChuljangCostList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
50 |
+ <p>출장비 현황</p> |
|
51 |
+ <div class="icon" v-if="isExactActive"> |
|
52 |
+ <img :src="menuicon" alt=""> |
|
53 |
+ </div> |
|
54 |
+ </router-link></li> |
|
55 |
+ <li> |
|
56 |
+ <router-link to="/projectInsert.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
57 |
+ <p>출장비 설정</p> |
|
58 |
+ <div class="icon" v-if="isExactActive"> |
|
59 |
+ <img :src="menuicon" alt=""> |
|
60 |
+ </div> |
|
61 |
+ </router-link> |
|
62 |
+ </li> |
|
63 |
+ <li> |
|
64 |
+ <router-link to="/MeetingCostList.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
65 |
+ <p>회의비 지출현황</p> |
|
66 |
+ <div class="icon" v-if="isExactActive"> |
|
67 |
+ <img :src="menuicon" alt=""> |
|
68 |
+ </div> |
|
69 |
+ </router-link> |
|
70 |
+ </li> |
|
71 |
+ |
|
72 |
+ </ul> |
|
73 |
+ </details> |
|
93 | 74 |
</div> |
94 |
- </section> |
|
75 |
+ </div> |
|
76 |
+ <!-- End Page Title --> |
|
77 |
+ <div class="content"> |
|
78 |
+ <router-view></router-view> |
|
79 |
+ |
|
80 |
+ </div> |
|
95 | 81 |
</template> |
96 | 82 |
|
97 | 83 |
<script> |
84 |
+import { ref } from 'vue'; |
|
98 | 85 |
|
99 | 86 |
export default { |
100 |
- components: { |
|
101 |
- }, |
|
102 | 87 |
data() { |
103 |
- console.log(localStorage.getItem('testKey')); |
|
104 |
- const today = new Date().toISOString().split('T')[0]; |
|
105 |
- const todayDatetime = new Date().toISOString().slice(0, 16); // To include time for datetime-local |
|
106 | 88 |
return { |
107 |
- editor: ClassicEditor, |
|
108 |
- editorData: '', // Data for the editor |
|
109 |
- title: '', // Bind to title input |
|
110 |
- notice: '', // Bind to notice radio buttons |
|
111 |
- startDate: todayDatetime, // Bind to start date |
|
112 |
- endDate: todayDatetime, // Bind to end date |
|
113 |
- isFormValid: true, // To track form validation status |
|
89 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
90 |
+ menuicon: "/client/resources/img/menuicon.png", |
|
91 |
+ topmenuicon: "/client/resources/img/topmenuicon.png", |
|
92 |
+ // 데이터 초기화 |
|
93 |
+ years: [2023, 2024, 2025], // 연도 목록 |
|
94 |
+ months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
95 |
+ selectedYear: '', |
|
96 |
+ selectedMonth: '', |
|
97 |
+ DeptData: [ |
|
98 |
+ { member: '', deptNM: '', acceptTerms: false }, |
|
99 |
+ // 더 많은 데이터 추가... |
|
100 |
+ ], |
|
101 |
+ filteredData: [], |
|
114 | 102 |
}; |
115 | 103 |
}, |
104 |
+ computed: { |
|
105 |
+ }, |
|
116 | 106 |
methods: { |
117 |
- handleInsert() { |
|
118 |
- console.log('Form Data:', { |
|
119 |
- title: this.title, |
|
120 |
- editorData: this.editorData, |
|
121 |
- notice: this.notice, |
|
122 |
- startDate: this.startDate, |
|
123 |
- endDate: this.endDate, |
|
124 |
- }); |
|
125 |
- }, |
|
126 |
- handleCancel() { |
|
127 |
- // Reset form fields |
|
128 |
- this.title = ''; |
|
129 |
- this.editorData = ''; |
|
130 |
- this.notice = ''; |
|
131 |
- this.startDate = this.endDate = new Date().toISOString().slice(0, 16); |
|
132 |
- // Optionally remove from localStorage |
|
133 |
- localStorage.removeItem('formData'); |
|
134 |
- }, |
|
135 |
- handleSubmit() { |
|
136 |
- console.log('handleSubmit called'); |
|
137 |
- // Validate the form before submission |
|
138 |
- this.validateForm(); |
|
139 |
- if (this.isFormValid) { |
|
140 |
- // Save form data to localStorage |
|
141 |
- const formData = { |
|
142 |
- title: this.title, |
|
143 |
- editorData: this.editorData, |
|
144 |
- notice: this.notice, |
|
145 |
- startDate: this.startDate, |
|
146 |
- endDate: this.endDate, |
|
147 |
- }; |
|
148 |
- localStorage.setItem('formData', JSON.stringify(formData)); |
|
149 |
- alert('등록되었습니다.'); |
|
150 |
- // Add further logic here (e.g., API call) |
|
151 |
- } else { |
|
152 |
- alert('모든 필드를 올바르게 작성해주세요.'); |
|
153 |
- } |
|
154 |
- }, |
|
155 |
- validateForm() { |
|
156 |
- // Check if all required fields are filled |
|
157 |
- this.isFormValid = !!( |
|
158 |
- this.title && |
|
159 |
- this.editorData && |
|
160 |
- this.notice && |
|
161 |
- this.startDate && |
|
162 |
- this.endDate |
|
163 |
- ); |
|
164 |
- }, |
|
165 |
- loadFormData() { |
|
166 |
- const savedData = localStorage.getItem('formData'); |
|
167 |
- console.log('savedData:', savedData); |
|
168 |
- if (savedData) { |
|
169 |
- const formData = JSON.parse(savedData); |
|
170 |
- this.title = formData.title || ''; |
|
171 |
- this.editorData = formData.editorData || ''; |
|
172 |
- this.notice = formData.notice || ''; |
|
173 |
- this.startDate = formData.startDate || ''; |
|
174 |
- this.endDate = formData.endDate || ''; |
|
175 |
- } |
|
107 |
+ |
|
108 |
+ // 페이지 변경 |
|
109 |
+ changePage(page) { |
|
110 |
+ this.currentPage = page; |
|
176 | 111 |
}, |
177 | 112 |
}, |
113 |
+ created() { |
|
114 |
+ }, |
|
178 | 115 |
mounted() { |
179 |
- this.loadFormData(); // Load saved data from localStorage |
|
116 |
+ |
|
180 | 117 |
}, |
181 | 118 |
}; |
182 | 119 |
</script> |
183 | 120 |
|
184 |
-<style scoped> |
|
185 |
-td, |
|
186 |
-th { |
|
187 |
- padding: 1rem; |
|
188 |
-} |
|
189 |
- |
|
190 |
-th { |
|
191 |
- width: 10rem; |
|
192 |
-} |
|
193 |
- |
|
194 |
-#file { |
|
195 |
- position: absolute; |
|
196 |
- width: 0; |
|
197 |
- height: 0; |
|
198 |
- padding: 0; |
|
199 |
- overflow: hidden; |
|
200 |
- border: 0; |
|
201 |
-} |
|
202 |
- |
|
203 |
-</style> |
|
121 |
+<style scoped></style> |
+++ client/views/pages/Manager/financial/salaryList.vue
... | ... | @@ -0,0 +1,177 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">급여명세서</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option :value="currentYear">{{ currentYear }}년</option> | |
10 | + <option value="all">전체</option> | |
11 | + <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> | |
12 | + {{ year }}년 | |
13 | + </option> | |
14 | + </select> | |
15 | + </div> | |
16 | + </div> | |
17 | + | |
18 | + <!-- Table --> | |
19 | + <div class="tbl-wrap"> | |
20 | + <table id="myTable" class="tbl data"> | |
21 | + <!-- 동적으로 <th> 생성 --> | |
22 | + <thead> | |
23 | + <tr> | |
24 | + <th>월 </th> | |
25 | + <th>지급액</th> | |
26 | + <th>공제액</th> | |
27 | + <th>수령액</th> | |
28 | + <th>지급일</th> | |
29 | + <th>명세서 보기</th> | |
30 | + </tr> | |
31 | + </thead> | |
32 | + <!-- 동적으로 <td> 생성 --> | |
33 | + <tbody> | |
34 | + <tr v-for="(item, index) in listData" :key="index"> | |
35 | + <td>{{ item.month }}</td> | |
36 | + <td>{{ formatCurrency(item.payment) }}</td> | |
37 | + <td>{{ formatCurrency(item.deduction) }}</td> | |
38 | + <td>{{ formatCurrency(item.actual) }}</td> | |
39 | + <td>{{ item.payDate }}</td> | |
40 | + <td> | |
41 | + <button class="btn secondary xsm" @click="showPopup = true">보기</button> | |
42 | + </td> | |
43 | + </tr> | |
44 | + </tbody> | |
45 | + </table> | |
46 | + <PaySlipPopup v-if="showPopup" @close="showPopup = false"/> | |
47 | + </div> | |
48 | + <div class="pagination"> | |
49 | + <ul> | |
50 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
51 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
52 | + < | |
53 | + </li> | |
54 | + | |
55 | + <!-- 페이지 번호 --> | |
56 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
57 | + @click="changePage(page)"> | |
58 | + {{ page }} | |
59 | + </li> | |
60 | + | |
61 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
62 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
63 | + > | |
64 | + </li> | |
65 | + </ul> | |
66 | + </div> | |
67 | + <!-- End Table --> | |
68 | + </div> | |
69 | + </div> | |
70 | + </div> | |
71 | +</template> | |
72 | + | |
73 | +<script> | |
74 | +import { ref } from 'vue'; | |
75 | +import PaySlipPopup from '../../../component/Popup/PaySlipPopup.vue'; | |
76 | +const currentYear = new Date().getFullYear(); | |
77 | +export default { | |
78 | + data() { | |
79 | + return { | |
80 | + showPopup: false, | |
81 | + currentYear, | |
82 | + selectedYear: currentYear, | |
83 | + remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), | |
84 | + showOptions: false, | |
85 | + currentPage: 1, | |
86 | + totalPages: 3, | |
87 | + photoicon: "/client/resources/img/photo_icon.png", | |
88 | + // 데이터 초기화 | |
89 | + years: [2023, 2024, 2025], // 연도 목록 | |
90 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
91 | + selectedYear: '', | |
92 | + selectedMonth: '', | |
93 | + listData: [ | |
94 | + { | |
95 | + month: '3', | |
96 | + payment: 3000000, | |
97 | + deduction: 500000, | |
98 | + actual: 2500000, | |
99 | + payDate: '2024-12-25', | |
100 | + slipUrl: '/slips/2024-12.pdf' // 예: 파일 경로나 상세 정보용 키 | |
101 | + }, | |
102 | + { | |
103 | + month: '4', | |
104 | + payment: 3100000, | |
105 | + deduction: 510000, | |
106 | + actual: 2590000, | |
107 | + payDate: '2025-01-25', | |
108 | + slipUrl: '/slips/2025-01.pdf' | |
109 | + } | |
110 | + ], | |
111 | + filteredData: [], | |
112 | + }; | |
113 | + }, | |
114 | + components: { | |
115 | + PaySlipPopup | |
116 | + }, | |
117 | + computed: { | |
118 | + }, | |
119 | + methods: { | |
120 | + formatCurrency(amount) { | |
121 | + return new Intl.NumberFormat('ko-KR').format(amount) + ' 원'; | |
122 | + }, | |
123 | + viewPayslip(item) { | |
124 | + // 예: 모달 열기, PDF 보기, 페이지 이동 등 | |
125 | + console.log('명세서 보기:', item); | |
126 | + // window.open(item.slipUrl, '_blank'); // 외부 링크 열기 예시 | |
127 | + }, | |
128 | + changePage(page) { | |
129 | + if (page < 1 || page > this.totalPages) return; | |
130 | + this.currentPage = page; | |
131 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
132 | + }, | |
133 | + async onClickSubmit() { | |
134 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
135 | + const { mutate, onDone, onError } = useMutation(mygql); | |
136 | + | |
137 | + try { | |
138 | + const result = await mutate(); | |
139 | + console.log(result); | |
140 | + } catch (error) { | |
141 | + console.error('Mutation error:', error); | |
142 | + } | |
143 | + }, | |
144 | + goToPage(type) { | |
145 | + if (type === '명세서') { | |
146 | + this.$router.push('/HyugaInsert.page'); | |
147 | + } else if (type === '출장') { | |
148 | + this.$router.push('/ChuljangDetail.page'); | |
149 | + } | |
150 | + }, | |
151 | + getStatusClass(status) { | |
152 | + if (status === '승인') return 'status-approved'; | |
153 | + if (status === '대기') return 'status-pending'; | |
154 | + return ''; | |
155 | + }, | |
156 | + isPastPeriod(period) { | |
157 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
158 | + const endDateStr = period.split('~')[1]?.trim(); | |
159 | + if (!endDateStr) return false; | |
160 | + | |
161 | + const endDate = new Date(endDateStr); | |
162 | + const today = new Date(); | |
163 | + | |
164 | + // 현재 날짜보다 과거면 true | |
165 | + return endDate < today; | |
166 | + } | |
167 | + }, | |
168 | + created() { | |
169 | + }, | |
170 | + mounted() { | |
171 | + | |
172 | + | |
173 | + }, | |
174 | +}; | |
175 | +</script> | |
176 | + | |
177 | +<style scoped></style> |
+++ client/views/pages/Manager/hr/buseoManagement.vue
... | ... | @@ -0,0 +1,331 @@ |
1 | +<template> | |
2 | + <div class="card"> | |
3 | + <div class="card-body"> | |
4 | + <h2 class="card-title">부서 관리</h2> | |
5 | + <!-- Multi Columns Form --> | |
6 | + <div class="flex align-top"> | |
7 | + <div class="sch-form-wrap search "> | |
8 | + <div v-for="(menu, index) in menus" :key="index" class="sidemenu"> | |
9 | + <details class="menu-box" open> | |
10 | + <summary class="topmenu"> | |
11 | + <img :src="arrow" alt="" class="arrow"> | |
12 | + <img :src="topmenuicon" alt=""> | |
13 | + <p>{{ menu.title }} </p> | |
14 | + <button @click="addSubMenu(index)" class="btn xsm secondary">sub +</button> | |
15 | + </summary> | |
16 | + <ul> | |
17 | + <li class="submenu" v-for="(submenu, subIndex) in menu.submenus" :key="subIndex"> | |
18 | + <router-link :to="submenu.link" exact-active-class="active-link" v-slot="{ isExactActive }"> | |
19 | + <img :src="menuicon" alt=""> | |
20 | + <p>{{ submenu.label }}</p> | |
21 | + </router-link> | |
22 | + </li> | |
23 | + </ul> | |
24 | + </details> | |
25 | + </div> | |
26 | + <div class="buttons"> | |
27 | + <button @click="addTopMenu"><img :src="addtopmenu" alt=""></button> | |
28 | + | |
29 | + </div> | |
30 | + </div> | |
31 | + <div style="width: 100%;"> | |
32 | + <div class=" sch-form-wrap title-wrap"> | |
33 | + <h3><img :src="h3icon" alt="">부서 정보</h3> | |
34 | + <div class="buttons" style="margin: 0;"> | |
35 | + <button type="submit" class="btn sm tertiary">초기화</button> | |
36 | + <button type="reset" class="btn sm secondary">등록</button> | |
37 | + <button type="delete" class="btn sm btn-red">삭제</button> | |
38 | + </div> | |
39 | + </div> | |
40 | + <form class="row g-3 pt-3 needs-validation detail" @submit.prevent="handleSubmit" | |
41 | + style="margin-bottom: 3rem;"> | |
42 | + <div class="col-12"> | |
43 | + <label for="purpose" class="form-label">상위부서</label> | |
44 | + <input type="text" class="form-control" id="purpose" v-model="purpose" readonly /> | |
45 | + </div> | |
46 | + <div class="col-12"> | |
47 | + <label for="purpose" class="form-label"> | |
48 | + <p>부서명 | |
49 | + <p class="require"><img :src="require" alt=""></p> | |
50 | + </p> | |
51 | + </label> | |
52 | + <input type="text" class="form-control" id="purpose" v-model="purpose" /> | |
53 | + </div> | |
54 | + | |
55 | + <div class="col-12 chuljang border-x"> | |
56 | + <label for="prvonsh" class="form-label">부서설명</label> | |
57 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
58 | + </div> | |
59 | + | |
60 | + | |
61 | + </form> | |
62 | + <div class=" sch-form-wrap title-wrap"> | |
63 | + <h3><img :src="h3icon" alt="">부서 사용자</h3> | |
64 | + <div class="buttons" style="margin: 0;"> | |
65 | + <button type="reset" class="btn sm secondary" @click="showPopup = true">사용자 추가</button> | |
66 | + <button type="delete" class="btn sm btn-red" @click="removeMember(index)">사용자 삭제</button> | |
67 | + </div> | |
68 | + <HrPopup v-if="showPopup" @close="showPopup = false" @select="addMember"/> | |
69 | + </div> | |
70 | + <div class="tbl-wrap chk-area"> | |
71 | + <table id="myTable" class="tbl data"> | |
72 | + | |
73 | + <thead> | |
74 | + <tr> | |
75 | + <th>선택</th> | |
76 | + <th>직급</th> | |
77 | + <th>이름</th> | |
78 | + <th>부서장</th> | |
79 | + </tr> | |
80 | + </thead> | |
81 | + <!-- 동적으로 <td> 생성 --> | |
82 | + <tbody> | |
83 | + <tr v-for="(member, index) in members" :key="index"> | |
84 | + <td> | |
85 | + <div class="form-check"> | |
86 | + <input type="checkbox" :id="`chk_${index}`" :value="member.name" v-model="member.checked" /> | |
87 | + <label :for="`chk_${index}`"></label> | |
88 | + </div> | |
89 | + </td> | |
90 | + <td>{{ member.position }}</td> | |
91 | + <td>{{ member.name }}</td> | |
92 | + <td> | |
93 | + <div class="form-check"> | |
94 | + <input type="radio" name="manager" :id="`rdo_${index}`" :value="member.name" | |
95 | + v-model="selectedManager" /> | |
96 | + <label :for="`rdo_${index}`"></label> | |
97 | + </div> | |
98 | + </td> | |
99 | + </tr> | |
100 | + </tbody> | |
101 | + </table> | |
102 | + | |
103 | + </div> | |
104 | + <div class="pagination"> | |
105 | + <ul> | |
106 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
107 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
108 | + < | |
109 | + </li> | |
110 | + | |
111 | + <!-- 페이지 번호 --> | |
112 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
113 | + @click="changePage(page)"> | |
114 | + {{ page }} | |
115 | + </li> | |
116 | + | |
117 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
118 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
119 | + > | |
120 | + </li> | |
121 | + </ul> | |
122 | + </div> | |
123 | + </div> | |
124 | + </div> | |
125 | + | |
126 | + | |
127 | + </div> | |
128 | + </div> | |
129 | +</template> | |
130 | + | |
131 | +<script> | |
132 | +import { ref } from 'vue'; | |
133 | +import { PlusCircleFilled, CloseOutlined, DownOutlined } from '@ant-design/icons-vue'; | |
134 | +import HrPopup from '../../../component/Popup/HrPopup.vue'; | |
135 | +const isOpen = ref(false) | |
136 | +export default { | |
137 | + data() { | |
138 | + const today = new Date().toISOString().split('T')[0]; | |
139 | + return { | |
140 | + selectedManager: '', | |
141 | + showPopup: false, | |
142 | + menus: [ | |
143 | + { | |
144 | + title: '부서1', | |
145 | + submenus: [ | |
146 | + { | |
147 | + label: '직원검색', | |
148 | + link: '/hrSearch.page', | |
149 | + }, | |
150 | + ], | |
151 | + }, | |
152 | + ], | |
153 | + currentPage: 1, | |
154 | + totalPages: 3, | |
155 | + members: [] , | |
156 | + selectedManager: null, | |
157 | + h3icon: "/client/resources/img/h3icon.png", | |
158 | + require: "/client/resources/img/require.png", | |
159 | + menuicon: "/client/resources/img/arrow-rg.png", | |
160 | + topmenuicon: "/client/resources/img/topmenu.png", | |
161 | + arrow: "/client/resources/img/arrow.png", | |
162 | + addtopmenu: "/client/resources/img/addtopmenu.png", | |
163 | + addsubmenu: "/client/resources/img/addsubmenu.png", | |
164 | + fileName: '', | |
165 | + startDate: today, | |
166 | + startTime: '09:00', | |
167 | + endDate: today, | |
168 | + endTime: '18:00', | |
169 | + where: '', | |
170 | + purpose: '', | |
171 | + approvals: [ | |
172 | + { | |
173 | + category: '결재', | |
174 | + name: '', | |
175 | + }, | |
176 | + ], | |
177 | + receipts: [ | |
178 | + { | |
179 | + type: '개인결제', | |
180 | + category: '결재', | |
181 | + category1: '구분', | |
182 | + }, | |
183 | + ], | |
184 | + }; | |
185 | + }, | |
186 | + components: { | |
187 | + PlusCircleFilled, CloseOutlined, DownOutlined, HrPopup | |
188 | + }, | |
189 | + computed: { | |
190 | + loginUser() { | |
191 | + const authStore = useAuthStore(); | |
192 | + return authStore.getLoginUser; | |
193 | + }, | |
194 | + }, | |
195 | + | |
196 | + methods: { | |
197 | + addMember(selectedUser) { | |
198 | + this.members.push({ | |
199 | + position: selectedUser.position, | |
200 | + name: selectedUser.name, // or other fields if needed | |
201 | + }); | |
202 | + this.showPopup = false; // 팝업 닫기 | |
203 | + }, | |
204 | + removeMember() { | |
205 | + this.members = this.members.filter(member => !member.checked); | |
206 | + }, | |
207 | + addTopMenu() { | |
208 | + const newIndex = this.menus.length + 1; | |
209 | + this.menus.push({ | |
210 | + title: `부서${newIndex}`, | |
211 | + submenus: [], | |
212 | + }); | |
213 | + }, | |
214 | + addSubMenu(menuIndex) { | |
215 | + this.menus[menuIndex].submenus.push({ | |
216 | + label: `신규메뉴${this.menus[menuIndex].submenus.length + 1}`, | |
217 | + link: '/new.page', | |
218 | + }); | |
219 | + }, | |
220 | + goToPage(type) { | |
221 | + if (type === '회의록 등록') { | |
222 | + this.$router.push('/meetingInsert.page'); | |
223 | + } else if (type === '출장') { | |
224 | + this.$router.push('/ChuljangDetail.page'); | |
225 | + } | |
226 | + }, | |
227 | + handleFileUpload(event) { | |
228 | + const file = event.target.files[0]; | |
229 | + if (file) { | |
230 | + this.fileName = file.name; | |
231 | + } | |
232 | + }, | |
233 | + addApproval() { | |
234 | + this.approvals.push({ | |
235 | + category: '결재', | |
236 | + name: '', | |
237 | + }); | |
238 | + }, | |
239 | + addReceipt() { | |
240 | + this.receipts.push({ | |
241 | + type: '개인결제', | |
242 | + category: '결재', | |
243 | + category1: '', | |
244 | + name: '', | |
245 | + file: null, | |
246 | + }); | |
247 | + }, | |
248 | + // 승인자 삭제 | |
249 | + removeApproval(index) { | |
250 | + this.approvals.splice(index, 1); | |
251 | + }, | |
252 | + removeReceipt(index) { | |
253 | + this.receipts.splice(index, 1); | |
254 | + }, | |
255 | + validateForm() { | |
256 | + // 필수 입력 필드 체크 | |
257 | + if ( | |
258 | + this.startDate && | |
259 | + this.startTime && | |
260 | + this.endDate && | |
261 | + this.endTime && | |
262 | + this.where && | |
263 | + this.purpose.trim() !== "" | |
264 | + ) { | |
265 | + this.isFormValid = true; | |
266 | + } else { | |
267 | + this.isFormValid = false; | |
268 | + } | |
269 | + }, | |
270 | + calculateDayCount() { | |
271 | + const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
272 | + const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
273 | + | |
274 | + let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
275 | + | |
276 | + if (this.startDate !== this.endDate) { | |
277 | + // 시작일과 종료일이 다른경우 | |
278 | + const startDateObj = new Date(this.startDate); | |
279 | + const endDateObj = new Date(this.endDate); | |
280 | + const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
281 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
282 | + this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
283 | + } else { | |
284 | + this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
285 | + } | |
286 | + } else { | |
287 | + // 시작일과 종료일이 같은 경우 | |
288 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
289 | + this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
290 | + } else { | |
291 | + this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
292 | + } | |
293 | + } | |
294 | + | |
295 | + this.validateForm(); // dayCount 변경 후 폼 재검증 | |
296 | + }, | |
297 | + handleSubmit() { | |
298 | + this.validateForm(); // 제출 시 유효성 확인 | |
299 | + if (this.isFormValid) { | |
300 | + localStorage.setItem('ChuljangFormData', JSON.stringify(this.$data)); | |
301 | + alert("승인 요청이 완료되었습니다."); | |
302 | + // 추가 처리 로직 (API 요청 등) | |
303 | + } else { | |
304 | + alert("모든 필드를 올바르게 작성해주세요."); | |
305 | + } | |
306 | + }, | |
307 | + loadFormData() { | |
308 | + const savedData = localStorage.getItem('ChuljangFormData'); | |
309 | + if (savedData) { | |
310 | + this.$data = JSON.parse(savedData); | |
311 | + } | |
312 | + }, | |
313 | + }, | |
314 | + mounted() { | |
315 | + // Load the saved form data when the page is loaded | |
316 | + this.loadFormData(); | |
317 | + }, | |
318 | + watch: { | |
319 | + startDate: 'calculateDayCount', | |
320 | + startTime: 'calculateDayCount', | |
321 | + endDate: 'calculateDayCount', | |
322 | + endTime: 'calculateDayCount', | |
323 | + where: 'validateForm', | |
324 | + purpose: "validateForm", | |
325 | + }, | |
326 | +}; | |
327 | +</script> | |
328 | + | |
329 | +<style scoped> | |
330 | +/* 필요한 스타일 추가 */ | |
331 | +</style> |
--- client/views/pages/Manager/hr/hr.vue
+++ client/views/pages/Manager/hr/hr.vue
... | ... | @@ -1,185 +1,99 @@ |
1 | 1 |
<template> |
2 |
- <div class="pagetitle"> |
|
3 |
- <h2>프로젝트 등록</h2> |
|
4 |
- </div><!-- End Page Title --> |
|
5 |
- |
|
6 |
- <section class="section"> |
|
7 |
- <div class="card"> |
|
8 |
- <div class="card-body"> |
|
9 |
- |
|
10 |
- <!-- Multi Columns Form --> |
|
11 |
- <form class="row g-3 pt-3" @submit.prevent="handleSubmit"> |
|
12 |
- <div class="col-md-9"> |
|
13 |
- <label for="projectNm" class="form-label">프로젝트명</label> |
|
14 |
- <input type="text" class="form-control" id="projectNm" v-model="projectNm" /> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo simple"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
15 | 8 |
</div> |
16 |
- <div class="col-md-5"> |
|
17 |
- <label for="orgNm" class="form-label">주관사</label> |
|
18 |
- <input type="text" class="form-control" id="orgNm" v-model="orgNm" /> |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
19 | 13 |
</div> |
20 |
- <div class="col-md-5"> |
|
21 |
- <label for="inputName5" class="form-label">부서</label> |
|
22 |
- <select id="inputState" class="form-select"> |
|
23 |
- <option selected>전략기획팀</option> |
|
24 |
- <option>개발팀</option> |
|
25 |
- <option>ux/ui</option> |
|
26 |
- </select> |
|
27 |
- </div> |
|
28 |
- <div class="col-md-9"> |
|
29 |
- <label for="rm" class="form-label">사업금액</label> |
|
30 |
- <input type="text" class="form-control" id="amount" v-model="amount" /> |
|
31 |
- </div> |
|
32 |
- <div class="col-md-5"> |
|
33 |
- <label for="startDate" class="form-label">사업기간</label> |
|
34 |
- <div class="d-flex gap-1"> |
|
35 |
- <input type="date" class="form-control" id="BstartDate" v-model="BstartDate" />~ |
|
36 |
- <input type="date" class="form-control" id="BendDate" v-model="BendDate" /> |
|
37 |
- </div> |
|
38 |
- </div> |
|
39 |
- <div class="col-6"> |
|
40 |
- <label for="prvonsh" class="form-label">사업 투입인력</label> |
|
41 |
- <div class="search-bar d-flex gap-2"> |
|
42 |
- <form class="search-form d-flex align-items-center" method="POST" action="#" |
|
43 |
- @submit.prevent="updateMember"> |
|
44 |
- <input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword"> |
|
45 |
- <button type="submit" title="Search"><PlusOutlined /></button> |
|
46 |
- </form> |
|
47 |
- <input type="text" class="select-member" :value="selectedMember.join(' ')" > |
|
48 |
- </div> |
|
49 |
- </div> |
|
50 |
- <div class="col-md-5"> |
|
51 |
- <label for="endDate" class="form-label">실제 투입기간</label> |
|
52 |
- <div class="d-flex gap-1"> |
|
53 |
- <input type="date" class="form-control" id="MystartDate" v-model="MystartDate" />~ |
|
54 |
- <input type="date" class="form-control" id="MyendDate" v-model="MyendDate" /> |
|
55 |
- |
|
56 |
- </div> |
|
57 |
- </div> |
|
58 |
- <div class="col-6"> |
|
59 |
- <label for="prvonsh" class="form-label">실제 투입인력</label> |
|
60 |
- <div class="search-bar d-flex gap-2"> |
|
61 |
- <form class="search-form d-flex align-items-center" method="POST" action="#" |
|
62 |
- @submit.prevent="updateMember"> |
|
63 |
- <input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword"> |
|
64 |
- <button type="submit" title="Search"><PlusOutlined /></button> |
|
65 |
- </form> |
|
66 |
- <input type="text" class="select-member" :value="selectedMember.join(' ')" > |
|
67 |
- </div> |
|
68 |
- </div> |
|
69 |
- <div class="col-md-4"> |
|
70 |
- <label for="totalDays" class="form-label">총 투입일<span class="small"></span></label> |
|
71 |
- <input type="text" class="form-control" id="totalDays" v-model="totalDays" readonly /> |
|
72 |
- </div> |
|
73 |
- |
|
74 |
- <div class="col-12"> |
|
75 |
- <label for="rm" class="form-label">비고</label> |
|
76 |
- <input type="text" class="form-control" id="rm" v-model="rm" /> |
|
77 |
- </div> |
|
78 |
- |
|
79 |
- <div class="text-end"> |
|
80 |
- <button type="submit" class="btn btn-primary" >등록</button> |
|
81 |
- <button type="reset" class="btn btn-secondary">취소</button> |
|
82 |
- </div> |
|
83 |
- </form><!-- End Multi Columns Form --> |
|
84 |
- |
|
14 |
+ </div> |
|
85 | 15 |
</div> |
16 |
+ |
|
17 |
+ |
|
18 |
+ <details class="menu-box"> |
|
19 |
+ <summary><p>직원</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
20 |
+ <ul> |
|
21 |
+ <li> <router-link to="/hrSearch.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
22 |
+ <p>직원검색</p> |
|
23 |
+ <div class="icon" v-if="isExactActive"> |
|
24 |
+ <img :src="menuicon" alt=""> |
|
25 |
+ </div> |
|
26 |
+ </router-link></li> |
|
27 |
+ <li> |
|
28 |
+ <router-link to="/hrManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
29 |
+ <p>직원관리</p> |
|
30 |
+ <div class="icon" v-if="isExactActive"> |
|
31 |
+ <img :src="menuicon" alt=""> |
|
32 |
+ </div> |
|
33 |
+ </router-link> |
|
34 |
+ </li> |
|
35 |
+ |
|
36 |
+ |
|
37 |
+ </ul> |
|
38 |
+ </details> |
|
39 |
+ <details class="menu-box"> |
|
40 |
+ <summary><p>부서</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
41 |
+ <ul> |
|
42 |
+ <li> <router-link to="/buseoManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
43 |
+ <p>부서관리</p> |
|
44 |
+ <div class="icon" v-if="isExactActive"> |
|
45 |
+ <img :src="menuicon" alt=""> |
|
46 |
+ </div> |
|
47 |
+ </router-link></li> |
|
48 |
+ |
|
49 |
+ |
|
50 |
+ </ul> |
|
51 |
+ </details> |
|
86 | 52 |
</div> |
87 |
- </section> |
|
53 |
+ </div> |
|
54 |
+ <!-- End Page Title --> |
|
55 |
+ <div class="content"> |
|
56 |
+ <router-view></router-view> |
|
57 |
+ |
|
58 |
+ </div> |
|
88 | 59 |
</template> |
89 | 60 |
|
90 | 61 |
<script> |
91 |
-import { PlusOutlined } from '@ant-design/icons-vue'; |
|
62 |
+import { ref } from 'vue'; |
|
92 | 63 |
|
93 | 64 |
export default { |
94 | 65 |
data() { |
95 |
- const today = new Date().toISOString().split('T')[0]; |
|
96 | 66 |
return { |
97 |
- searchQuery: '', |
|
98 |
- selectedMember: [], |
|
99 |
- projectNm: '', // Project name |
|
100 |
- orgNm: '', // Organization name |
|
101 |
- department: '', // Department selection |
|
102 |
- amount: '', |
|
103 |
- rm: '', // Business amount |
|
104 |
- BstartDate: today, // Business start date |
|
105 |
- BendDate: today, // Business end date |
|
106 |
- MystartDate: today, // Actual start date |
|
107 |
- MyendDate: today, // Actual end date |
|
108 |
- totalDays: 1, // Total days |
|
109 |
- reason: "", // Notes or remarks |
|
67 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
68 |
+ menuicon: "/client/resources/img/menuicon.png", |
|
69 |
+ topmenuicon: "/client/resources/img/topmenuicon.png", |
|
70 |
+ // 데이터 초기화 |
|
71 |
+ years: [2023, 2024, 2025], // 연도 목록 |
|
72 |
+ months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
73 |
+ selectedYear: '', |
|
74 |
+ selectedMonth: '', |
|
75 |
+ DeptData: [ |
|
76 |
+ { member: '', deptNM: '', acceptTerms: false }, |
|
77 |
+ // 더 많은 데이터 추가... |
|
78 |
+ ], |
|
79 |
+ filteredData: [], |
|
110 | 80 |
}; |
111 | 81 |
}, |
112 |
- components: { |
|
113 |
- PlusOutlined, |
|
114 |
- }, |
|
115 | 82 |
computed: { |
116 |
- // Pinia Store의 상태를 가져옵니다. |
|
117 |
- loginUser() { |
|
118 |
- const authStore = useAuthStore(); |
|
119 |
- return authStore.getLoginUser; |
|
120 |
- }, |
|
121 | 83 |
}, |
122 | 84 |
methods: { |
123 |
- updateMember() { |
|
124 |
- // Add the search query to the selectedMembers array if it's not empty |
|
125 |
- if (this.searchQuery.trim()) { |
|
126 |
- this.selectedMember.push(this.searchQuery.trim()); |
|
127 |
- } |
|
128 |
- // Clear the search query after adding it to selectedMembers |
|
129 |
- this.searchQuery = ''; |
|
130 |
- }, |
|
131 |
- // 폼 검증 메서드 |
|
132 |
- validateForm() { |
|
133 |
- // 필수 입력 필드 체크 |
|
134 |
- if ( |
|
135 |
- this.BstartDate && |
|
136 |
- this.BendDate && |
|
137 |
- this.MystartDate && |
|
138 |
- this.MyendDate && |
|
139 |
- this.totalDays > 0 |
|
140 |
- ) { |
|
141 |
- this.isFormValid = true; |
|
142 |
- } else { |
|
143 |
- this.isFormValid = false; |
|
144 |
- } |
|
85 |
+ |
|
86 |
+ // 페이지 변경 |
|
87 |
+ changePage(page) { |
|
88 |
+ this.currentPage = page; |
|
145 | 89 |
}, |
146 |
- |
|
147 |
- handleSubmit() { |
|
148 |
- this.validateForm(); // Validate the form when submitting |
|
149 |
- if (this.isFormValid) { |
|
150 |
- // Save form data to localStorage |
|
151 |
- localStorage.setItem('projectFormData', JSON.stringify(this.$data)); |
|
152 |
- alert("승인 요청이 완료되었습니다."); |
|
153 |
- // Additional processing logic (e.g., API request) |
|
154 |
- } else { |
|
155 |
- alert("모든 필드를 올바르게 작성해주세요."); |
|
156 |
- } |
|
157 |
- }, |
|
158 |
- loadFormData() { |
|
159 |
- const savedData = localStorage.getItem('projectFormData'); |
|
160 |
- if (savedData) { |
|
161 |
- this.$data = JSON.parse(savedData); |
|
162 |
- } |
|
163 |
- console.log(loadFormData) |
|
164 |
- }, |
|
165 |
- |
|
90 |
+ }, |
|
91 |
+ created() { |
|
166 | 92 |
}, |
167 | 93 |
mounted() { |
168 |
- // Load the saved form data when the page is loaded |
|
169 |
- this.loadFormData(); |
|
94 |
+ |
|
170 | 95 |
}, |
171 |
- watch: { |
|
172 |
- projectNm: "saveFormData", |
|
173 |
- orgNm: "saveFormData", |
|
174 |
- department: "saveFormData", |
|
175 |
- rm: "saveFormData", |
|
176 |
- BstartDate: "saveFormData", |
|
177 |
- BendDate: "saveFormData", |
|
178 |
- MystartDate: "saveFormData", |
|
179 |
- MyendDate: "saveFormData", |
|
180 |
- totalDays: "saveFormData", |
|
181 |
- reason: "saveFormData", |
|
182 |
- }, |
|
183 |
- |
|
184 | 96 |
}; |
185 | 97 |
</script> |
98 |
+ |
|
99 |
+<style scoped></style> |
+++ client/views/pages/Manager/hr/hrDetail.vue
... | ... | @@ -0,0 +1,193 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">직원 검색</h2> | |
5 | + | |
6 | + | |
7 | + <div class="name-box flex simple"> | |
8 | + <div class="img-area" > | |
9 | + <div class="img"><img :src="photoicon" alt=""> | |
10 | + </div> | |
11 | + </div> | |
12 | + <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" | |
13 | + @submit.prevent="handleRegister" novalidate > | |
14 | + <div class="col-12"> | |
15 | + <label for="yourName" class="form-label">아이디</label> | |
16 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
17 | + placeholder="admin"> | |
18 | + </div> | |
19 | + <div class="col-12 "> | |
20 | + <div class="col-12 border-x"> | |
21 | + <label for="youremail" class="form-label ">이름<p class="require"><img :src="require" alt=""></p></label> | |
22 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required | |
23 | + readonly> | |
24 | + </div> | |
25 | + | |
26 | + <div class="col-12 border-x"> | |
27 | + <label for="yourPassword" class="form-label">부서</label> | |
28 | + <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" | |
29 | + required readonly placeholder="주식회사 테이큰 소프트"> | |
30 | + </div> | |
31 | + </div> | |
32 | + <div class="col-12 border-x"> | |
33 | + <div class="col-12 border-x"> | |
34 | + <label for="youremail" class="form-label">직급</label> | |
35 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly | |
36 | + placeholder="과장"> | |
37 | + </div> | |
38 | + | |
39 | + <div class="col-12 border-x"> | |
40 | + <label for="yourPassword" class="form-label">직책</label> | |
41 | + <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" | |
42 | + required readonly placeholder="팀장"> | |
43 | + </div> | |
44 | + </div> | |
45 | + | |
46 | + | |
47 | + </form> | |
48 | + | |
49 | + </div> | |
50 | + <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" | |
51 | + @submit.prevent="handleRegister" novalidate style="margin-bottom: 20px;"> | |
52 | + <div class="col-12"> | |
53 | + <label for="yourName" class="form-label">연락처</label> | |
54 | + <input v-model="name" type="text" name="name" class="form-control " id="yourName" required readonly | |
55 | + placeholder="admin"> | |
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" id="yourName" required readonly | |
60 | + placeholder="admin"> | |
61 | + </div> | |
62 | + <div class="col-12 border-x"> | |
63 | + <label for="yourName" class="form-label">입사일</label> | |
64 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
65 | + placeholder="admin"> | |
66 | + </div> | |
67 | + | |
68 | + | |
69 | + | |
70 | + </form> | |
71 | + <form class="row g-3 needs-validation detail salary" :class="{ 'was-validated': formSubmitted }" | |
72 | + @submit.prevent="handleRegister" novalidate> | |
73 | + <div class=" col-12 border-x"><label>연봉</label> | |
74 | + <div class="yearsalary"> | |
75 | + <div class="col-12"> | |
76 | + <label for="" class="second-label">2023</label> | |
77 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
78 | + placeholder="admin"> | |
79 | + </div> | |
80 | + <div class="col-12"> | |
81 | + <label for="" class="second-label">2023</label> | |
82 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
83 | + placeholder="admin"> | |
84 | + </div> | |
85 | + <div class="col-12 border-x"> | |
86 | + <label for="" class="second-label">2023</label> | |
87 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
88 | + placeholder="admin"> | |
89 | + </div> | |
90 | + </div> | |
91 | + </div> | |
92 | + </form> | |
93 | + <div class="buttons"> | |
94 | + <button type="delete" class="btn sm btn-red">탈퇴</button> | |
95 | + <button type="reset" class="btn sm secondary">수정</button> | |
96 | + <button type="submit" class="btn sm tertiary">목록</button> | |
97 | + </div> | |
98 | + </div> | |
99 | + | |
100 | + </div> | |
101 | + | |
102 | +</template> | |
103 | + | |
104 | +<script> | |
105 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
106 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
107 | +export default { | |
108 | + data() { | |
109 | + return { | |
110 | + photoicon: "/client/resources/img/photo_icon.png", | |
111 | + img1: "/client/resources/img/img.png", | |
112 | + icon1: "/client/resources/img/icon.png", | |
113 | + dateicon: "/client/resources/img/date.png", | |
114 | + startbtn: "/client/resources/img/start.png", | |
115 | + stopbtn: "/client/resources/img/stop.png", | |
116 | + moreicon: "/client/resources/img/more.png", | |
117 | + today: new Date().toLocaleDateString('ko-KR', { | |
118 | + year: 'numeric', | |
119 | + month: '2-digit', | |
120 | + day: '2-digit', | |
121 | + weekday: 'short', | |
122 | + }), | |
123 | + time: this.getCurrentTime(), | |
124 | + listData: Array.from({ length: 20 }, (_, i) => ({ | |
125 | + department: `부서 ${i + 1}`, | |
126 | + name: `이름 ${i + 1}`, | |
127 | + position: `직급 ${i + 1}` | |
128 | + })) | |
129 | + } | |
130 | + }, | |
131 | + components: { | |
132 | + SearchOutlined | |
133 | + }, | |
134 | + methods: { | |
135 | + formatBudget(amount) { | |
136 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
137 | + }, | |
138 | + isPastPeriod(period) { | |
139 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
140 | + const endDateStr = period.split('~')[1]?.trim(); | |
141 | + if (!endDateStr) return false; | |
142 | + | |
143 | + const endDate = new Date(endDateStr); | |
144 | + const today = new Date(); | |
145 | + | |
146 | + // 현재 날짜보다 과거면 true | |
147 | + return endDate < today; | |
148 | + }, | |
149 | + getStatusClass(status) { | |
150 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
151 | + }, | |
152 | + getStatusClass(status) { | |
153 | + if (status === '미진행') return 'status-pending'; | |
154 | + if (status === '진행중') return 'status-approved'; | |
155 | + | |
156 | + // Default empty string | |
157 | + return ''; | |
158 | + }, | |
159 | + getCurrentTime() { | |
160 | + const now = new Date(); | |
161 | + const hours = String(now.getHours()).padStart(2, '0'); | |
162 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
163 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
164 | + return `${hours}:${minutes}:${seconds}`; | |
165 | + }, | |
166 | + getCategoryClass(category) { | |
167 | + switch (category) { | |
168 | + case '용역': return 'category-service'; | |
169 | + case '내부': return 'category-internal'; | |
170 | + case '국가과제': return 'category-government'; | |
171 | + default: return ''; | |
172 | + } | |
173 | + }, | |
174 | + }, | |
175 | + watch: { | |
176 | + | |
177 | + }, | |
178 | + computed: { | |
179 | + | |
180 | + }, | |
181 | + mounted() { | |
182 | + console.log('main mounted'); | |
183 | + setInterval(() => { | |
184 | + this.time = this.getCurrentTime(); | |
185 | + }, 1000); | |
186 | + } | |
187 | +} | |
188 | +</script> | |
189 | +<style scoped> | |
190 | +tr { | |
191 | + cursor: pointer; | |
192 | +} | |
193 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/hr/hrInsert.vue
... | ... | @@ -0,0 +1,262 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">직원 검색</h2> | |
5 | + | |
6 | + | |
7 | + <div class="name-box flex simple"> | |
8 | + <div class="img-area column"> | |
9 | + <div class="img"> | |
10 | + <img :src="previewImg || placeholder" alt="미리보기 이미지" /> | |
11 | + <button class="close-btn" @click="removeImage">×</button> | |
12 | + </div> | |
13 | + <div class="info"> | |
14 | + <div class="file"> | |
15 | + <label for="fileUpload" class="file-label"> | |
16 | + <img :src="file" alt=""> | |
17 | + <p>업로드</p> | |
18 | + </label> | |
19 | + <input id="fileUpload" type="file" @change="handleFileUpload" accept="image/*" /> | |
20 | + </div> | |
21 | + </div> | |
22 | + </div> | |
23 | + <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" | |
24 | + @submit.prevent="handleRegister" novalidate > | |
25 | + <div class="col-12 "> | |
26 | + <div class="col-12 border-x"> | |
27 | + <label for="youremail" class="form-label "><p>아이디<p class="require"><img :src="require" alt=""></p></p></label> | |
28 | + <input v-model="email" type="text" name="username" class="form-control" required | |
29 | + > | |
30 | + </div> | |
31 | + | |
32 | + <div class="col-12 border-x"> | |
33 | + <label for="yourPassword" class="form-label"><p>권한<p class="require"><img :src="require" alt=""></p></p></label> | |
34 | + <select class="form-select" > | |
35 | + <option value="선택">선택</option> | |
36 | + <option value=""></option> | |
37 | + <option value=""></option> | |
38 | + </select> | |
39 | + </div> | |
40 | + </div> | |
41 | + <div class="col-12 "> | |
42 | + <div class="col-12 border-x"> | |
43 | + <label for="youremail" class="form-label "><p>이름<p class="require"><img :src="require" alt=""></p></p></label> | |
44 | + <input v-model="email" type="text" name="username" class="form-control" required | |
45 | + > | |
46 | + </div> | |
47 | + | |
48 | + <div class="col-12 border-x"> | |
49 | + <label for="yourPassword" class="form-label"><p>부서<p class="require"><img :src="require" alt=""></p></p></label> | |
50 | + <input v-model="selectedname" type="password" name="password" class="form-control" | |
51 | + required placeholder="주식회사 테이큰 소프트"> | |
52 | + <input type="button" class="form-control " value="검색" @click="showPopup = true" /> | |
53 | + <BuseoPopup v-if="showPopup" @close="showPopup = false" @select="addApproval"/> | |
54 | + </div> | |
55 | + </div> | |
56 | + <div class="col-12 border-x"> | |
57 | + <div class="col-12 border-x"> | |
58 | + <label for="youremail" class="form-label"><p>직급<p class="require"><img :src="require" alt=""></p></p></label> | |
59 | + <select class="form-select" > | |
60 | + <option value="선택">선택</option> | |
61 | + <option value=""></option> | |
62 | + <option value=""></option> | |
63 | + </select> | |
64 | + </div> | |
65 | + | |
66 | + <div class="col-12 border-x"> | |
67 | + <label for="yourPassword" class="form-label">직책</label> | |
68 | + <select class="form-select" > | |
69 | + <option value="선택">선택</option> | |
70 | + <option value=""></option> | |
71 | + <option value=""></option> | |
72 | + </select> | |
73 | + </div> | |
74 | + </div> | |
75 | + | |
76 | + | |
77 | + </form> | |
78 | + | |
79 | + </div> | |
80 | + <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" | |
81 | + @submit.prevent="handleRegister" novalidate style="margin-bottom: 20px;"> | |
82 | + <div class="col-12"> | |
83 | + <label for="yourName" class="form-label">연락처</label> | |
84 | + <input v-model="name" type="text" name="name" class="form-control " id="yourName" | |
85 | + placeholder="admin"> | |
86 | + </div> | |
87 | + <div class="col-12"> | |
88 | + <label for="yourName" class="form-label">생년월일</label> | |
89 | + <input v-model="name" type="date" name="name" class="form-control" id="yourName" | |
90 | + > | |
91 | + </div> | |
92 | + <div class="col-12 border-x"> | |
93 | + <label for="yourName" class="form-label"><p>입사일<p class="require"><img :src="require" alt=""></p></p></label> | |
94 | + <input v-model="name" type="date" name="name" class="form-control" id="yourName" required | |
95 | + > | |
96 | + </div> | |
97 | + | |
98 | + | |
99 | + | |
100 | + </form> | |
101 | + <form class="row g-3 needs-validation detail salary" :class="{ 'was-validated': formSubmitted }" | |
102 | + @submit.prevent="handleRegister" novalidate> | |
103 | + <div class=" col-12 border-x"><label>연봉<button type="button" title="추가" @click="addSalary"> | |
104 | + <PlusCircleFilled /> | |
105 | + </button></label> | |
106 | + <div class="yearsalary approval-container"> | |
107 | + <div class="col-12 border-x addapproval" v-for="(salary, index) in salarys" :key="index"> | |
108 | + <input type="text" name="name" class="form-control" v-model="salary.salary" style="width: 200px;" | |
109 | + placeholder="년도"> | |
110 | + <div> | |
111 | + <input type="text" name="name" class="form-control" v-model="salary.total" | |
112 | + placeholder="금액"> | |
113 | + </div> | |
114 | + </div> | |
115 | + | |
116 | + </div> | |
117 | + </div> | |
118 | + </form> | |
119 | + <div class="buttons"> | |
120 | + <button type="reset" class="btn sm primary">등록</button> | |
121 | + <button type="submit" class="btn sm tertiary">취소</button> | |
122 | + </div> | |
123 | + </div> | |
124 | + | |
125 | + </div> | |
126 | + | |
127 | +</template> | |
128 | + | |
129 | +<script> | |
130 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
131 | +import { SearchOutlined, PlusCircleFilled } from '@ant-design/icons-vue'; | |
132 | +import BuseoPopup from "../../../component/Popup/BuseoPopup.vue"; | |
133 | +export default { | |
134 | + data() { | |
135 | + return { | |
136 | + showPopup: false, | |
137 | + selectedname: '', | |
138 | + approvals: [], | |
139 | + salarys: [ { | |
140 | + salary: '', | |
141 | + total: '', | |
142 | + },] , | |
143 | + previewImg: null, | |
144 | + placeholder: "/client/resources/img/img1.png", | |
145 | + require: "/client/resources/img/require.png", | |
146 | + file: "/client/resources/img/file.png", | |
147 | + photoicon: "/client/resources/img/photo_icon.png", | |
148 | + img1: "/client/resources/img/img.png", | |
149 | + icon1: "/client/resources/img/icon.png", | |
150 | + dateicon: "/client/resources/img/date.png", | |
151 | + startbtn: "/client/resources/img/start.png", | |
152 | + stopbtn: "/client/resources/img/stop.png", | |
153 | + moreicon: "/client/resources/img/more.png", | |
154 | + today: new Date().toLocaleDateString('ko-KR', { | |
155 | + year: 'numeric', | |
156 | + month: '2-digit', | |
157 | + day: '2-digit', | |
158 | + weekday: 'short', | |
159 | + }), | |
160 | + time: this.getCurrentTime(), | |
161 | + listData: Array.from({ length: 20 }, (_, i) => ({ | |
162 | + department: `부서 ${i + 1}`, | |
163 | + name: `이름 ${i + 1}`, | |
164 | + position: `직급 ${i + 1}` | |
165 | + })) | |
166 | + } | |
167 | + }, | |
168 | + components: { | |
169 | + SearchOutlined, PlusCircleFilled, BuseoPopup | |
170 | + }, | |
171 | + methods: { | |
172 | + addApproval(selectedUser) { | |
173 | + this.approvals.push({ | |
174 | + name: selectedUser.name | |
175 | + }); | |
176 | + | |
177 | + this.selectedname = selectedUser.name; // 입력창에 표시 | |
178 | + this.showPopup = false; | |
179 | + }, | |
180 | + addSalary() { | |
181 | + this.salarys.push({ | |
182 | + salary: '', | |
183 | + total: '', | |
184 | + }); | |
185 | + }, | |
186 | + handleFileUpload(event) { | |
187 | + const file = event.target.files[0]; | |
188 | + if (file && file.type.startsWith('image/')) { | |
189 | + const reader = new FileReader(); | |
190 | + reader.onload = (e) => { | |
191 | + this.previewImg = e.target.result; // 파일 읽기 완료 후 미리보기 이미지 설정 | |
192 | + }; | |
193 | + reader.readAsDataURL(file); // 파일을 데이터 URL로 읽기 | |
194 | + } else { | |
195 | + alert('이미지 파일만 선택할 수 있습니다.'); | |
196 | + } | |
197 | + }, | |
198 | + | |
199 | + // 이미지 삭제 함수 | |
200 | + removeImage() { | |
201 | + this.previewImg = null; // 미리보기 이미지 삭제 | |
202 | + this.$refs.fileUpload.value = null; // 파일 input 초기화 | |
203 | + }, | |
204 | + formatBudget(amount) { | |
205 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
206 | + }, | |
207 | + isPastPeriod(period) { | |
208 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
209 | + const endDateStr = period.split('~')[1]?.trim(); | |
210 | + if (!endDateStr) return false; | |
211 | + | |
212 | + const endDate = new Date(endDateStr); | |
213 | + const today = new Date(); | |
214 | + | |
215 | + // 현재 날짜보다 과거면 true | |
216 | + return endDate < today; | |
217 | + }, | |
218 | + getStatusClass(status) { | |
219 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
220 | + }, | |
221 | + getStatusClass(status) { | |
222 | + if (status === '미진행') return 'status-pending'; | |
223 | + if (status === '진행중') return 'status-approved'; | |
224 | + | |
225 | + // Default empty string | |
226 | + return ''; | |
227 | + }, | |
228 | + getCurrentTime() { | |
229 | + const now = new Date(); | |
230 | + const hours = String(now.getHours()).padStart(2, '0'); | |
231 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
232 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
233 | + return `${hours}:${minutes}:${seconds}`; | |
234 | + }, | |
235 | + getCategoryClass(category) { | |
236 | + switch (category) { | |
237 | + case '용역': return 'category-service'; | |
238 | + case '내부': return 'category-internal'; | |
239 | + case '국가과제': return 'category-government'; | |
240 | + default: return ''; | |
241 | + } | |
242 | + }, | |
243 | + }, | |
244 | + watch: { | |
245 | + | |
246 | + }, | |
247 | + computed: { | |
248 | + | |
249 | + }, | |
250 | + mounted() { | |
251 | + console.log('main mounted'); | |
252 | + setInterval(() => { | |
253 | + this.time = this.getCurrentTime(); | |
254 | + }, 1000); | |
255 | + } | |
256 | +} | |
257 | +</script> | |
258 | +<style scoped> | |
259 | +tr { | |
260 | + cursor: pointer; | |
261 | +} | |
262 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/hr/hrManagement.vue
... | ... | @@ -0,0 +1,175 @@ |
1 | +<template> | |
2 | + <div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">직원관리</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option value="">부서</option> | |
10 | + </select> | |
11 | + <select name="" id="" class="form-select"> | |
12 | + <option value="">직급</option> | |
13 | + </select> | |
14 | + <select name="" id="" class="form-select"> | |
15 | + <option value="">직책</option> | |
16 | + </select> | |
17 | + <div class="sch-input"> | |
18 | + <input type="text" class="form-control" placeholder="직원명"> | |
19 | + <button class="ico-sch"><SearchOutlined /></button> | |
20 | + </div> | |
21 | + </div> | |
22 | + </div> | |
23 | + | |
24 | + <!-- Table --> | |
25 | + <div class="tbl-wrap"> | |
26 | + <table id="myTable" class="tbl data"> | |
27 | + <!-- 동적으로 <th> 생성 --> | |
28 | + <thead> | |
29 | + <tr> | |
30 | + <th>부서 </th> | |
31 | + <th>직급</th> | |
32 | + <th>직책</th> | |
33 | + <th>아이디</th> | |
34 | + <th>이름</th> | |
35 | + <th>입사일</th> | |
36 | + <th>비밀번호 초기화</th> | |
37 | + </tr> | |
38 | + </thead> | |
39 | + <!-- 동적으로 <td> 생성 --> | |
40 | + <tbody> | |
41 | + <tr v-for="(item, index) in listData" :key="index" @click="goToDetailPage(item)"> | |
42 | + <td>{{ item.department }}</td> | |
43 | + <td>{{ item.position }}</td> | |
44 | + <td>{{ item.role }}</td> | |
45 | + <td>{{ item.userId }}</td> | |
46 | + <td>{{ item.name }}</td> | |
47 | + <td>{{ item.joinDate }}</td> | |
48 | + <td> | |
49 | + <button class="btn tertiary xsm" @click.stop="resetPassword(item)">초기화</button> | |
50 | + </td> | |
51 | + </tr> | |
52 | + </tbody> | |
53 | + </table> | |
54 | + | |
55 | + </div> | |
56 | + <div class="pagination"> | |
57 | + <ul> | |
58 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
59 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
60 | + < | |
61 | + </li> | |
62 | + | |
63 | + <!-- 페이지 번호 --> | |
64 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
65 | + @click="changePage(page)"> | |
66 | + {{ page }} | |
67 | + </li> | |
68 | + | |
69 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
70 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
71 | + > | |
72 | + </li> | |
73 | + </ul> | |
74 | + </div> | |
75 | + <div class="buttons"> | |
76 | + <button class="btn primary" type="submit" @click="goToPage('등록')">등록</button> | |
77 | + </div> | |
78 | + </div> | |
79 | + </div> | |
80 | + </div> | |
81 | +</template> | |
82 | + | |
83 | +<script> | |
84 | +import { ref } from 'vue'; | |
85 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
86 | +export default { | |
87 | + data() { | |
88 | + return { | |
89 | + showOptions: false, | |
90 | + currentPage: 1, | |
91 | + totalPages: 3, | |
92 | + photoicon: "/client/resources/img/photo_icon.png", | |
93 | + // 데이터 초기화 | |
94 | + years: [2023, 2024, 2025], // 연도 목록 | |
95 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
96 | + selectedYear: '', | |
97 | + selectedMonth: '', | |
98 | + listData: [ | |
99 | + { | |
100 | + department: '인사팀', | |
101 | + position: '대리', | |
102 | + role: '인사담당', | |
103 | + userId: 'honggildong', | |
104 | + name: '홍길동', | |
105 | + joinDate: '2022-01-15' | |
106 | + }, | |
107 | +], | |
108 | + filteredData: [], | |
109 | + }; | |
110 | + }, | |
111 | + components:{ | |
112 | + SearchOutlined | |
113 | + }, | |
114 | + computed: { | |
115 | + }, | |
116 | + methods: { | |
117 | + resetPassword(user) { | |
118 | + // 예: 비밀번호 초기화 로직 | |
119 | + console.log(`${user.name} (${user.userId}) 비밀번호 초기화`); | |
120 | + // 실제 초기화 API 호출 또는 처리 추가 | |
121 | + }, | |
122 | + goToDetailPage(item) { | |
123 | + // item.id 또는 다른 식별자를 사용하여 URL을 구성할 수 있습니다. | |
124 | + this.$router.push({ path: `/hrDetail.page`, query: { id: item.id } }); | |
125 | + }, | |
126 | + changePage(page) { | |
127 | + if (page < 1 || page > this.totalPages) return; | |
128 | + this.currentPage = page; | |
129 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
130 | + }, | |
131 | + async onClickSubmit() { | |
132 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
133 | + const { mutate, onDone, onError } = useMutation(mygql); | |
134 | + | |
135 | + try { | |
136 | + const result = await mutate(); | |
137 | + console.log(result); | |
138 | + } catch (error) { | |
139 | + console.error('Mutation error:', error); | |
140 | + } | |
141 | + }, | |
142 | + goToPage(type) { | |
143 | + if (type === '등록') { | |
144 | + this.$router.push('/hrInsert.page'); | |
145 | + } | |
146 | + }, | |
147 | + getStatusClass(status) { | |
148 | + if (status === '승인') return 'status-approved'; | |
149 | + if (status === '대기') return 'status-pending'; | |
150 | + return ''; | |
151 | + }, | |
152 | + isPastPeriod(period) { | |
153 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
154 | + const endDateStr = period.split('~')[1]?.trim(); | |
155 | + if (!endDateStr) return false; | |
156 | + | |
157 | + const endDate = new Date(endDateStr); | |
158 | + const today = new Date(); | |
159 | + | |
160 | + // 현재 날짜보다 과거면 true | |
161 | + return endDate < today; | |
162 | + } | |
163 | + }, | |
164 | + created() { | |
165 | + }, | |
166 | + mounted() { | |
167 | + | |
168 | + | |
169 | + }, | |
170 | +}; | |
171 | +</script> | |
172 | + | |
173 | +<style scoped> | |
174 | +tr{cursor: pointer;} | |
175 | +</style> |
+++ client/views/pages/Manager/hr/hrSearch.vue
... | ... | @@ -0,0 +1,200 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">직원 검색</h2> | |
5 | + <div class="flex align-top"> | |
6 | + <div class="sch-form-wrap search"> | |
7 | + <div class="input-group"> | |
8 | + <div class="sch-input"> | |
9 | + <input type="text" class="form-control" placeholder="직원명"> | |
10 | + <button class="ico-sch"> | |
11 | + <SearchOutlined /> | |
12 | + </button> | |
13 | + </div> | |
14 | + </div> | |
15 | + <div class="tbl-wrap table-scroll"> | |
16 | + <table id="myTable" class="tbl data"> | |
17 | + <!-- 동적으로 <th> 생성 --> | |
18 | + <thead> | |
19 | + <tr> | |
20 | + <th>부서 </th> | |
21 | + <th>이름</th> | |
22 | + <th>직급(직책)</th> | |
23 | + </tr> | |
24 | + </thead> | |
25 | + <!-- 동적으로 <td> 생성 --> | |
26 | + <tbody> | |
27 | + <tr v-for="(item, index) in listData" :key="index"> | |
28 | + <td></td> | |
29 | + <td></td> | |
30 | + <td></td> | |
31 | + </tr> | |
32 | + </tbody> | |
33 | + </table> | |
34 | + | |
35 | + </div> | |
36 | + </div> | |
37 | + | |
38 | + <div> | |
39 | + <div class="name-box flex sb simple"> | |
40 | + <div class="img-area" style="width: 170px;"> | |
41 | + <div class="img"><img :src="photoicon" alt=""> | |
42 | + </div> | |
43 | + </div> | |
44 | + <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" | |
45 | + @submit.prevent="handleRegister" novalidate> | |
46 | + <div class="col-12"> | |
47 | + <label for="yourName" class="form-label">아이디</label> | |
48 | + <input v-model="name" type="text" name="name" class="form-control" required readonly | |
49 | + placeholder="admin"> | |
50 | + </div> | |
51 | + <div class="col-12 "> | |
52 | + <div class="col-12 border-x"> | |
53 | + <label for="youremail" class="form-label ">이름<p class="require"><img :src="require" alt=""></p></label> | |
54 | + <input v-model="email" type="text" name="username" class="form-control" required | |
55 | + readonly> | |
56 | + </div> | |
57 | + | |
58 | + <div class="col-12 border-x"> | |
59 | + <label for="yourPassword" class="form-label">부서</label> | |
60 | + <input v-model="password" type="password" name="password" class="form-control" | |
61 | + required readonly placeholder="주식회사 테이큰 소프트"> | |
62 | + </div> | |
63 | + </div> | |
64 | + <div class="col-12 border-x"> | |
65 | + <div class="col-12 border-x"> | |
66 | + <label for="youremail" class="form-label">직급</label> | |
67 | + <input v-model="email" type="text" name="username" class="form-control" required readonly | |
68 | + placeholder="과장"> | |
69 | + </div> | |
70 | + | |
71 | + <div class="col-12 border-x"> | |
72 | + <label for="yourPassword" class="form-label">직책</label> | |
73 | + <input v-model="password" type="password" name="password" class="form-control" | |
74 | + required readonly placeholder="팀장"> | |
75 | + </div> | |
76 | + </div> | |
77 | + | |
78 | + | |
79 | + </form> | |
80 | + | |
81 | + </div> | |
82 | + <form class="row g-3 needs-validation detail" :class="{ 'was-validated': formSubmitted }" | |
83 | + @submit.prevent="handleRegister" novalidate> | |
84 | + <div class="col-12"> | |
85 | + <label for="yourName" class="form-label">연락처</label> | |
86 | + <input v-model="name" type="text" name="name" class="form-control" required readonly | |
87 | + placeholder="admin"> | |
88 | + </div> | |
89 | + <div class="col-12"> | |
90 | + <label for="yourName" class="form-label">생년월일</label> | |
91 | + <input v-model="name" type="text" name="name" class="form-control" required readonly | |
92 | + placeholder="admin"> | |
93 | + </div> | |
94 | + <div class="col-12 border-x"> | |
95 | + <label for="yourName" class="form-label">입사일</label> | |
96 | + <input v-model="name" type="text" name="name" class="form-control" required readonly | |
97 | + placeholder="admin"> | |
98 | + </div> | |
99 | + | |
100 | + | |
101 | + | |
102 | + </form> | |
103 | + </div> | |
104 | + </div> | |
105 | + </div> | |
106 | + | |
107 | + </div> | |
108 | + | |
109 | +</template> | |
110 | + | |
111 | +<script> | |
112 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
113 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
114 | +export default { | |
115 | + data() { | |
116 | + return { | |
117 | + photoicon: "/client/resources/img/photo_icon.png", | |
118 | + img1: "/client/resources/img/img.png", | |
119 | + icon1: "/client/resources/img/icon.png", | |
120 | + dateicon: "/client/resources/img/date.png", | |
121 | + startbtn: "/client/resources/img/start.png", | |
122 | + stopbtn: "/client/resources/img/stop.png", | |
123 | + moreicon: "/client/resources/img/more.png", | |
124 | + today: new Date().toLocaleDateString('ko-KR', { | |
125 | + year: 'numeric', | |
126 | + month: '2-digit', | |
127 | + day: '2-digit', | |
128 | + weekday: 'short', | |
129 | + }), | |
130 | + time: this.getCurrentTime(), | |
131 | + listData: Array.from({ length: 20 }, (_, i) => ({ | |
132 | + department: `부서 ${i + 1}`, | |
133 | + name: `이름 ${i + 1}`, | |
134 | + position: `직급 ${i + 1}` | |
135 | + })) | |
136 | + } | |
137 | + }, | |
138 | + components: { | |
139 | + SearchOutlined | |
140 | + }, | |
141 | + methods: { | |
142 | + formatBudget(amount) { | |
143 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
144 | + }, | |
145 | + isPastPeriod(period) { | |
146 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
147 | + const endDateStr = period.split('~')[1]?.trim(); | |
148 | + if (!endDateStr) return false; | |
149 | + | |
150 | + const endDate = new Date(endDateStr); | |
151 | + const today = new Date(); | |
152 | + | |
153 | + // 현재 날짜보다 과거면 true | |
154 | + return endDate < today; | |
155 | + }, | |
156 | + getStatusClass(status) { | |
157 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
158 | + }, | |
159 | + getStatusClass(status) { | |
160 | + if (status === '미진행') return 'status-pending'; | |
161 | + if (status === '진행중') return 'status-approved'; | |
162 | + | |
163 | + // Default empty string | |
164 | + return ''; | |
165 | + }, | |
166 | + getCurrentTime() { | |
167 | + const now = new Date(); | |
168 | + const hours = String(now.getHours()).padStart(2, '0'); | |
169 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
170 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
171 | + return `${hours}:${minutes}:${seconds}`; | |
172 | + }, | |
173 | + getCategoryClass(category) { | |
174 | + switch (category) { | |
175 | + case '용역': return 'category-service'; | |
176 | + case '내부': return 'category-internal'; | |
177 | + case '국가과제': return 'category-government'; | |
178 | + default: return ''; | |
179 | + } | |
180 | + }, | |
181 | + }, | |
182 | + watch: { | |
183 | + | |
184 | + }, | |
185 | + computed: { | |
186 | + | |
187 | + }, | |
188 | + mounted() { | |
189 | + console.log('main mounted'); | |
190 | + setInterval(() => { | |
191 | + this.time = this.getCurrentTime(); | |
192 | + }, 1000); | |
193 | + } | |
194 | +} | |
195 | +</script> | |
196 | +<style scoped> | |
197 | +tr { | |
198 | + cursor: pointer; | |
199 | +} | |
200 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/system/accessControlManagement.vue
... | ... | @@ -0,0 +1,224 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">접근제어관리</h2> | |
5 | + <div class="flex align-top"> | |
6 | + <div class="sch-form-wrap search"> | |
7 | + | |
8 | + <div class="tbl-wrap table-scroll"> | |
9 | + <table id="myTable" class="tbl data"> | |
10 | + <!-- 동적으로 <th> 생성 --> | |
11 | + <thead> | |
12 | + <tr> | |
13 | + <th>권한목록 </th> | |
14 | + </tr> | |
15 | + </thead> | |
16 | + <!-- 동적으로 <td> 생성 --> | |
17 | + <tbody> | |
18 | + <tr v-for="(item, index) in listData" :key="index"> | |
19 | + <td></td> | |
20 | + </tr> | |
21 | + </tbody> | |
22 | + </table> | |
23 | + | |
24 | + </div> | |
25 | + </div> | |
26 | + | |
27 | + <div style="width: 100%;"> | |
28 | + <div class="tbl-wrap chk-area"> | |
29 | + <table id="myTable" class="tbl data"> | |
30 | + | |
31 | + <thead> | |
32 | + <tr> | |
33 | + <th>메뉴명</th> | |
34 | + <th>전체</th> | |
35 | + <th>읽기</th> | |
36 | + <th>쓰기</th> | |
37 | + <th>수정</th> | |
38 | + <th>삭제</th> | |
39 | + </tr> | |
40 | + </thead> | |
41 | + <!-- 동적으로 <td> 생성 --> | |
42 | + <tbody> | |
43 | + <tr v-for="(item, index) in listData" :key="index"> | |
44 | + <td>{{ item.menuName }}</td> | |
45 | + <td> | |
46 | + <div class="form-check"> | |
47 | + <input | |
48 | + type="checkbox" | |
49 | + :id="`all_${index}`" | |
50 | + v-model="item.permissions.all" | |
51 | + @change="toggleAll(index)" | |
52 | + /> | |
53 | + <label :for="`all_${index}`"></label> | |
54 | + </div> | |
55 | + </td> | |
56 | + <td> | |
57 | + <div class="form-check"> | |
58 | + <input | |
59 | + type="checkbox" | |
60 | + :id="`read_${index}`" | |
61 | + v-model="item.permissions.read" | |
62 | + /> | |
63 | + <label :for="`read_${index}`"></label> | |
64 | + </div> | |
65 | + </td> | |
66 | + <td> | |
67 | + <div class="form-check"> | |
68 | + <input | |
69 | + type="checkbox" | |
70 | + :id="`write_${index}`" | |
71 | + v-model="item.permissions.write" | |
72 | + /> | |
73 | + <label :for="`write_${index}`"></label> | |
74 | + </div> | |
75 | + </td> | |
76 | + <td> | |
77 | + <div class="form-check"> | |
78 | + <input | |
79 | + type="checkbox" | |
80 | + :id="`edit_${index}`" | |
81 | + v-model="item.permissions.edit" | |
82 | + /> | |
83 | + <label :for="`edit_${index}`"></label> | |
84 | + </div> | |
85 | + </td> | |
86 | + <td> | |
87 | + <div class="form-check"> | |
88 | + <input | |
89 | + type="checkbox" | |
90 | + :id="`delete_${index}`" | |
91 | + v-model="item.permissions.delete" | |
92 | + /> | |
93 | + <label :for="`delete_${index}`"></label> | |
94 | + </div> | |
95 | + </td> | |
96 | + </tr> | |
97 | + </tbody> | |
98 | + </table> | |
99 | + | |
100 | + </div> | |
101 | + </div> | |
102 | + </div> | |
103 | + </div> | |
104 | + | |
105 | + </div> | |
106 | + | |
107 | +</template> | |
108 | + | |
109 | +<script> | |
110 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
111 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
112 | +export default { | |
113 | + data() { | |
114 | + return { | |
115 | + require: "/client/resources/img/require.png", | |
116 | + h3icon: "/client/resources/img/h3icon.png", | |
117 | + photoicon: "/client/resources/img/photo_icon.png", | |
118 | + img1: "/client/resources/img/img.png", | |
119 | + icon1: "/client/resources/img/icon.png", | |
120 | + dateicon: "/client/resources/img/date.png", | |
121 | + startbtn: "/client/resources/img/start.png", | |
122 | + stopbtn: "/client/resources/img/stop.png", | |
123 | + moreicon: "/client/resources/img/more.png", | |
124 | + today: new Date().toLocaleDateString('ko-KR', { | |
125 | + year: 'numeric', | |
126 | + month: '2-digit', | |
127 | + day: '2-digit', | |
128 | + weekday: 'short', | |
129 | + }), | |
130 | + time: this.getCurrentTime(), | |
131 | + listData: [ | |
132 | + { | |
133 | + menuName: '메뉴1', | |
134 | + permissions: { | |
135 | + all: false, | |
136 | + read: false, | |
137 | + write: false, | |
138 | + edit: false, | |
139 | + delete: false, | |
140 | + }, | |
141 | + }, | |
142 | + { | |
143 | + menuName: '메뉴2', | |
144 | + permissions: { | |
145 | + all: false, | |
146 | + read: false, | |
147 | + write: false, | |
148 | + edit: false, | |
149 | + delete: false, | |
150 | + }, | |
151 | + }, | |
152 | + ] | |
153 | + } | |
154 | + }, | |
155 | + components: { | |
156 | + SearchOutlined | |
157 | + }, | |
158 | + methods: { | |
159 | + toggleAll(index) { | |
160 | + const all = this.listData[index].permissions.all; | |
161 | + this.listData[index].permissions.read = all; | |
162 | + this.listData[index].permissions.write = all; | |
163 | + this.listData[index].permissions.edit = all; | |
164 | + this.listData[index].permissions.delete = all; | |
165 | + }, | |
166 | + formatBudget(amount) { | |
167 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
168 | + }, | |
169 | + isPastPeriod(period) { | |
170 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
171 | + const endDateStr = period.split('~')[1]?.trim(); | |
172 | + if (!endDateStr) return false; | |
173 | + | |
174 | + const endDate = new Date(endDateStr); | |
175 | + const today = new Date(); | |
176 | + | |
177 | + // 현재 날짜보다 과거면 true | |
178 | + return endDate < today; | |
179 | + }, | |
180 | + getStatusClass(status) { | |
181 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
182 | + }, | |
183 | + getStatusClass(status) { | |
184 | + if (status === '미진행') return 'status-pending'; | |
185 | + if (status === '진행중') return 'status-approved'; | |
186 | + | |
187 | + // Default empty string | |
188 | + return ''; | |
189 | + }, | |
190 | + getCurrentTime() { | |
191 | + const now = new Date(); | |
192 | + const hours = String(now.getHours()).padStart(2, '0'); | |
193 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
194 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
195 | + return `${hours}:${minutes}:${seconds}`; | |
196 | + }, | |
197 | + getCategoryClass(category) { | |
198 | + switch (category) { | |
199 | + case '용역': return 'category-service'; | |
200 | + case '내부': return 'category-internal'; | |
201 | + case '국가과제': return 'category-government'; | |
202 | + default: return ''; | |
203 | + } | |
204 | + }, | |
205 | + }, | |
206 | + watch: { | |
207 | + | |
208 | + }, | |
209 | + computed: { | |
210 | + | |
211 | + }, | |
212 | + mounted() { | |
213 | + console.log('main mounted'); | |
214 | + setInterval(() => { | |
215 | + this.time = this.getCurrentTime(); | |
216 | + }, 1000); | |
217 | + } | |
218 | +} | |
219 | +</script> | |
220 | +<style scoped> | |
221 | +tr { | |
222 | + cursor: pointer; | |
223 | +} | |
224 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/system/commonCodeDetail.vue
... | ... | @@ -0,0 +1,144 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">사용자권한관리</h2> | |
5 | + <p class="require"><img :src="require" alt=""> 필수입력</p> | |
6 | + <form class="row g-3 pt-3 needs-validation detail" @submit.prevent="handleSubmit" | |
7 | + style="margin-bottom: 3rem;"> | |
8 | + <div class="col-12"> | |
9 | + <div class="col-12 border-x"> | |
10 | + <label for="where" class="form-label">상위코드</label> | |
11 | + <input type="text" class="form-control" id="where" v-model="where" readonly /> | |
12 | + </div> | |
13 | + <div class="col-12 border-x"> | |
14 | + <label for="where" class="form-label">상위코드 명</label> | |
15 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
16 | + </div> | |
17 | + | |
18 | + </div> | |
19 | + <div class="col-12"> | |
20 | + <div class="col-12 border-x"> | |
21 | + <label for="where" class="form-label">코드</label> | |
22 | + <input type="text" class="form-control" id="where" v-model="where"readonly /> | |
23 | + </div> | |
24 | + <div class="col-12 border-x"> | |
25 | + <label for="where" class="form-label">코드명</label> | |
26 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
27 | + </div> | |
28 | + | |
29 | + </div> | |
30 | + | |
31 | + <div class="col-12 chuljang "> | |
32 | + <label for="prvonsh" class="form-label">설명</label> | |
33 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" readonly/> | |
34 | + </div> | |
35 | + <div class="col-12 border-x input-radio"> | |
36 | + <label for="prvonsh" class="form-label">사용여부</label> | |
37 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
38 | + </div> | |
39 | + | |
40 | + | |
41 | + </form> | |
42 | + <div class="buttons"> | |
43 | + <button type="submit" class="btn btn-red">삭제</button> | |
44 | + <button type="submit" class="btn secondary">수정</button> | |
45 | + <button type="reset" class="btn tertiary">목록</button> | |
46 | + </div> | |
47 | + </div> | |
48 | + | |
49 | + </div> | |
50 | + | |
51 | +</template> | |
52 | + | |
53 | +<script> | |
54 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
55 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
56 | +export default { | |
57 | + data() { | |
58 | + return { | |
59 | + require: "/client/resources/img/require.png", | |
60 | + h3icon: "/client/resources/img/h3icon.png", | |
61 | + photoicon: "/client/resources/img/photo_icon.png", | |
62 | + img1: "/client/resources/img/img.png", | |
63 | + icon1: "/client/resources/img/icon.png", | |
64 | + dateicon: "/client/resources/img/date.png", | |
65 | + startbtn: "/client/resources/img/start.png", | |
66 | + stopbtn: "/client/resources/img/stop.png", | |
67 | + moreicon: "/client/resources/img/more.png", | |
68 | + today: new Date().toLocaleDateString('ko-KR', { | |
69 | + year: 'numeric', | |
70 | + month: '2-digit', | |
71 | + day: '2-digit', | |
72 | + weekday: 'short', | |
73 | + }), | |
74 | + time: this.getCurrentTime(), | |
75 | + listData: Array.from({ length: 20 }, (_, i) => ({ | |
76 | + department: `부서 ${i + 1}`, | |
77 | + name: `이름 ${i + 1}`, | |
78 | + position: `직급 ${i + 1}` | |
79 | + })) | |
80 | + } | |
81 | + }, | |
82 | + components: { | |
83 | + SearchOutlined | |
84 | + }, | |
85 | + methods: { | |
86 | + formatBudget(amount) { | |
87 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
88 | + }, | |
89 | + isPastPeriod(period) { | |
90 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
91 | + const endDateStr = period.split('~')[1]?.trim(); | |
92 | + if (!endDateStr) return false; | |
93 | + | |
94 | + const endDate = new Date(endDateStr); | |
95 | + const today = new Date(); | |
96 | + | |
97 | + // 현재 날짜보다 과거면 true | |
98 | + return endDate < today; | |
99 | + }, | |
100 | + getStatusClass(status) { | |
101 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
102 | + }, | |
103 | + getStatusClass(status) { | |
104 | + if (status === '미진행') return 'status-pending'; | |
105 | + if (status === '진행중') return 'status-approved'; | |
106 | + | |
107 | + // Default empty string | |
108 | + return ''; | |
109 | + }, | |
110 | + getCurrentTime() { | |
111 | + const now = new Date(); | |
112 | + const hours = String(now.getHours()).padStart(2, '0'); | |
113 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
114 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
115 | + return `${hours}:${minutes}:${seconds}`; | |
116 | + }, | |
117 | + getCategoryClass(category) { | |
118 | + switch (category) { | |
119 | + case '용역': return 'category-service'; | |
120 | + case '내부': return 'category-internal'; | |
121 | + case '국가과제': return 'category-government'; | |
122 | + default: return ''; | |
123 | + } | |
124 | + }, | |
125 | + }, | |
126 | + watch: { | |
127 | + | |
128 | + }, | |
129 | + computed: { | |
130 | + | |
131 | + }, | |
132 | + mounted() { | |
133 | + console.log('main mounted'); | |
134 | + setInterval(() => { | |
135 | + this.time = this.getCurrentTime(); | |
136 | + }, 1000); | |
137 | + } | |
138 | +} | |
139 | +</script> | |
140 | +<style scoped> | |
141 | +tr { | |
142 | + cursor: pointer; | |
143 | +} | |
144 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/system/commonCodeInsert.vue
... | ... | @@ -0,0 +1,168 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">사용자권한관리</h2> | |
5 | + <p class="require"><img :src="require" alt=""> 필수입력</p> | |
6 | + <form class=" needs-validation detail" @submit.prevent="handleSubmit" | |
7 | + style="margin-bottom: 3rem;"> | |
8 | + <div class="col-12"> | |
9 | + <div class="col-12 border-x"> | |
10 | + <label for="where" class="form-label">상위코드</label> | |
11 | + <input type="text" class="form-control" id="where" v-model="selectedparentCode" /> | |
12 | + <input type="button" class="form-control " value="검색" @click="showPopup = true" /> | |
13 | + <CodePopup v-if="showPopup" @close="showPopup = false" @select="addCode" /> | |
14 | + </div> | |
15 | + <div class="col-12 border-x"> | |
16 | + <label for="where" class="form-label">상위코드 명</label> | |
17 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
18 | + </div> | |
19 | + | |
20 | + </div> | |
21 | + <div class="col-12"> | |
22 | + <div class="col-12 border-x"> | |
23 | + <label for="where" class="form-label"><p>코드<p class="require"><img :src="require" alt=""></p></p></label> | |
24 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
25 | + </div> | |
26 | + <div class="col-12 border-x"> | |
27 | + <label for="where" class="form-label"><p>코드명<p class="require"><img :src="require" alt=""></p></p></label> | |
28 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
29 | + </div> | |
30 | + | |
31 | + </div> | |
32 | + | |
33 | + <div class="col-12 chuljang "> | |
34 | + <label for="prvonsh" class="form-label">설명</label> | |
35 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
36 | + </div> | |
37 | + <div class="col-12 border-x input-radio"> | |
38 | + <label for="prvonsh" class="form-label"> <p>사용여부 | |
39 | + <p class="require"><img :src="require" alt=""></p> | |
40 | + </p></label> | |
41 | + <div class="chk-area"> | |
42 | + <div class="form-check"> | |
43 | + <input type="radio" name="rdo_1" id="rdo_1"> | |
44 | + <label for="rdo_1">사용</label> | |
45 | + </div> | |
46 | + <div class="form-check"> | |
47 | + <input type="radio" name="rdo_1" id="rdo_2" checked> | |
48 | + <label for="rdo_2">미사용</label> | |
49 | + </div> | |
50 | + </div> | |
51 | + </div> | |
52 | + | |
53 | + | |
54 | + </form> | |
55 | + <div class="buttons"> | |
56 | + <button type="submit" class="btn primary">등록</button> | |
57 | + <button type="reset" class="btn tertiary">취소</button> | |
58 | + </div> | |
59 | + </div> | |
60 | + | |
61 | + </div> | |
62 | + | |
63 | +</template> | |
64 | + | |
65 | +<script> | |
66 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
67 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
68 | +import CodePopup from "../../../component/Popup/CodePopup.vue"; | |
69 | +export default { | |
70 | + data() { | |
71 | + return { | |
72 | + showPopup: true, | |
73 | + codes: [], | |
74 | + selectedparentCode: '', | |
75 | + require: "/client/resources/img/require.png", | |
76 | + h3icon: "/client/resources/img/h3icon.png", | |
77 | + photoicon: "/client/resources/img/photo_icon.png", | |
78 | + img1: "/client/resources/img/img.png", | |
79 | + icon1: "/client/resources/img/icon.png", | |
80 | + dateicon: "/client/resources/img/date.png", | |
81 | + startbtn: "/client/resources/img/start.png", | |
82 | + stopbtn: "/client/resources/img/stop.png", | |
83 | + moreicon: "/client/resources/img/more.png", | |
84 | + today: new Date().toLocaleDateString('ko-KR', { | |
85 | + year: 'numeric', | |
86 | + month: '2-digit', | |
87 | + day: '2-digit', | |
88 | + weekday: 'short', | |
89 | + }), | |
90 | + time: this.getCurrentTime(), | |
91 | + listData: Array.from({ length: 20 }, (_, i) => ({ | |
92 | + department: `부서 ${i + 1}`, | |
93 | + name: `이름 ${i + 1}`, | |
94 | + position: `직급 ${i + 1}` | |
95 | + })) | |
96 | + } | |
97 | + }, | |
98 | + components: { | |
99 | + SearchOutlined, CodePopup | |
100 | + }, | |
101 | + methods: { | |
102 | + addCode(selectedUser) { | |
103 | + this.codes.push({ | |
104 | + parentCode: selectedUser.parentCode, | |
105 | + }); | |
106 | + | |
107 | + this.selectedparentCode = selectedUser.parentCode; // 입력창에 표시 | |
108 | + this.showPopup = false; | |
109 | + }, | |
110 | + formatBudget(amount) { | |
111 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
112 | + }, | |
113 | + isPastPeriod(period) { | |
114 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
115 | + const endDateStr = period.split('~')[1]?.trim(); | |
116 | + if (!endDateStr) return false; | |
117 | + | |
118 | + const endDate = new Date(endDateStr); | |
119 | + const today = new Date(); | |
120 | + | |
121 | + // 현재 날짜보다 과거면 true | |
122 | + return endDate < today; | |
123 | + }, | |
124 | + getStatusClass(status) { | |
125 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
126 | + }, | |
127 | + getStatusClass(status) { | |
128 | + if (status === '미진행') return 'status-pending'; | |
129 | + if (status === '진행중') return 'status-approved'; | |
130 | + | |
131 | + // Default empty string | |
132 | + return ''; | |
133 | + }, | |
134 | + getCurrentTime() { | |
135 | + const now = new Date(); | |
136 | + const hours = String(now.getHours()).padStart(2, '0'); | |
137 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
138 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
139 | + return `${hours}:${minutes}:${seconds}`; | |
140 | + }, | |
141 | + getCategoryClass(category) { | |
142 | + switch (category) { | |
143 | + case '용역': return 'category-service'; | |
144 | + case '내부': return 'category-internal'; | |
145 | + case '국가과제': return 'category-government'; | |
146 | + default: return ''; | |
147 | + } | |
148 | + }, | |
149 | + }, | |
150 | + watch: { | |
151 | + | |
152 | + }, | |
153 | + computed: { | |
154 | + | |
155 | + }, | |
156 | + mounted() { | |
157 | + console.log('main mounted'); | |
158 | + setInterval(() => { | |
159 | + this.time = this.getCurrentTime(); | |
160 | + }, 1000); | |
161 | + } | |
162 | +} | |
163 | +</script> | |
164 | +<style scoped> | |
165 | +tr { | |
166 | + cursor: pointer; | |
167 | +} | |
168 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/system/commonCodeManagement.vue
... | ... | @@ -0,0 +1,189 @@ |
1 | +<template> | |
2 | +<div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">공통코드관리</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option value="">전체</option> | |
10 | + <option value="">상위코드</option> | |
11 | + <option value="">상위코드명</option> | |
12 | + <option value="">코드</option> | |
13 | + <option value="">코드명</option> | |
14 | + </select> | |
15 | + <div class="sch-input"> | |
16 | + <input type="text" class="form-control"> | |
17 | + <button class="ico-sch"> | |
18 | + <SearchOutlined /> | |
19 | + </button> | |
20 | + </div> | |
21 | + </div> | |
22 | + </div> | |
23 | + | |
24 | + <!-- Table --> | |
25 | + <div class="tbl-wrap"> | |
26 | + <table id="myTable" class="tbl data"> | |
27 | + <!-- 동적으로 <th> 생성 --> | |
28 | + <thead> | |
29 | + <tr> | |
30 | + <th>상위코드 </th> | |
31 | + <th>상위코드명</th> | |
32 | + <th>코드</th> | |
33 | + <th>코드명</th> | |
34 | + <th>등록일</th> | |
35 | + <th>사용여부</th> | |
36 | + </tr> | |
37 | + </thead> | |
38 | + <!-- 동적으로 <td> 생성 --> | |
39 | + <tbody> | |
40 | + <tr v-for="(item, index) in listData" :key="index" :class="{ expired: !isUsed(item.used) }" @click="goToDetailPage(item)"> | |
41 | + <td>{{ item.parentCode }}</td> | |
42 | + <td>{{ item.parentName }}</td> | |
43 | + <td>{{ item.code }}</td> | |
44 | + <td>{{ item.name }}</td> | |
45 | + <td>{{ item.createdAt }}</td> | |
46 | + <td :class="getStatusClass(item.used)">{{ item.used }}</td> | |
47 | + </tr> | |
48 | + </tbody> | |
49 | + </table> | |
50 | + | |
51 | + </div> | |
52 | + <div class="pagination"> | |
53 | + <ul> | |
54 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
55 | + <li | |
56 | + class="arrow" | |
57 | + :class="{ disabled: currentPage === 1 }" | |
58 | + @click="changePage(currentPage - 1)" | |
59 | + > | |
60 | + < | |
61 | + </li> | |
62 | + | |
63 | + <!-- 페이지 번호 --> | |
64 | + <li | |
65 | + v-for="page in totalPages" | |
66 | + :key="page" | |
67 | + :class="{ active: currentPage === page }" | |
68 | + @click="changePage(page)" | |
69 | + > | |
70 | + {{ page }} | |
71 | + </li> | |
72 | + | |
73 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
74 | + <li | |
75 | + class="arrow" | |
76 | + :class="{ disabled: currentPage === totalPages }" | |
77 | + @click="changePage(currentPage + 1)" | |
78 | + > | |
79 | + > | |
80 | + </li> | |
81 | + </ul> | |
82 | + </div> | |
83 | + <!-- End Table --> | |
84 | + <div class="buttons"> | |
85 | + <button type="button" class="btn sm primary" @click="goToPage('등록') "> | |
86 | + 등록 | |
87 | + </button> | |
88 | + | |
89 | + <!-- 신청 종류 선택 모달 --> | |
90 | + <div v-if="showOptions" class="modal-overlay"> | |
91 | + <div class="modal-box"> | |
92 | + <p>신청 종류를 선택하세요</p> | |
93 | + <button @click="goToPage('휴가')">휴가신청</button> | |
94 | + <button @click="goToPage('출장')">출장신청</button> | |
95 | + <button class="cancel" @click="showOptions = false">취소</button> | |
96 | + </div> | |
97 | + </div> | |
98 | + </div> | |
99 | + </div> | |
100 | + </div> | |
101 | +</div> | |
102 | +</template> | |
103 | + | |
104 | +<script> | |
105 | +import { ref } from 'vue'; | |
106 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
107 | +export default { | |
108 | + data() { | |
109 | + return { | |
110 | + showOptions: false, | |
111 | + currentPage: 1, | |
112 | + totalPages: 3, | |
113 | + photoicon: "/client/resources/img/photo_icon.png", | |
114 | + // 데이터 초기화 | |
115 | + years: [2023, 2024, 2025], // 연도 목록 | |
116 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
117 | + selectedYear: '', | |
118 | + selectedMonth: '', | |
119 | + listData: [ | |
120 | + { | |
121 | + parentCode: 'A01', | |
122 | + parentName: '부서코드', | |
123 | + code: 'A0101', | |
124 | + name: '총무부', | |
125 | + createdAt: '2024-01-10', | |
126 | + used: 'Y', | |
127 | + }, | |
128 | + { | |
129 | + parentCode: 'A01', | |
130 | + parentName: '부서코드', | |
131 | + code: 'A0102', | |
132 | + name: '인사부', | |
133 | + createdAt: '2024-03-21', | |
134 | + used: 'N', | |
135 | + },], | |
136 | + filteredData: [], | |
137 | + }; | |
138 | + }, | |
139 | + computed: { | |
140 | + }, | |
141 | + components: { | |
142 | + SearchOutlined | |
143 | + }, | |
144 | + methods: { | |
145 | + goToDetailPage(item) { | |
146 | + // item.id 또는 다른 식별자를 사용하여 URL을 구성할 수 있습니다. | |
147 | + this.$router.push({ path: `/commonCodeDetail.page`, query: { id: item.id } }); | |
148 | + }, | |
149 | + changePage(page) { | |
150 | + if (page < 1 || page > this.totalPages) return; | |
151 | + this.currentPage = page; | |
152 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
153 | + }, | |
154 | + async onClickSubmit() { | |
155 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
156 | + const { mutate, onDone, onError } = useMutation(mygql); | |
157 | + | |
158 | + try { | |
159 | + const result = await mutate(); | |
160 | + console.log(result); | |
161 | + } catch (error) { | |
162 | + console.error('Mutation error:', error); | |
163 | + } | |
164 | + }, | |
165 | + goToPage(type) { | |
166 | + if (type === '등록') { | |
167 | + this.$router.push('/commonCodeInsert.page'); | |
168 | + } | |
169 | +}, | |
170 | +getStatusClass(used) { | |
171 | + return used === 'Y' ? 'status-approved' : 'status-pending'; | |
172 | + }, | |
173 | + | |
174 | + isUsed(used) { | |
175 | + return used === 'Y'; | |
176 | + }, | |
177 | + }, | |
178 | + created() { | |
179 | + }, | |
180 | + mounted() { | |
181 | + | |
182 | + | |
183 | + }, | |
184 | +}; | |
185 | +</script> | |
186 | + | |
187 | +<style scoped> | |
188 | +tr{cursor: pointer;} | |
189 | +</style> |
--- client/views/pages/Manager/system/system.vue
+++ client/views/pages/Manager/system/system.vue
... | ... | @@ -1,180 +1,85 @@ |
1 | 1 |
<template> |
2 |
- <div class="pagetitle"> |
|
3 |
- <h2>프로젝트 내역</h2> |
|
4 |
- </div> |
|
5 |
- <!-- End Page Title --> |
|
6 |
- <section class="section"> |
|
7 |
- <div class="row"> |
|
8 |
- <!-- 해당년도 연차 수 --> |
|
9 |
- <!-- 전체 --> |
|
10 |
- <div class="col-xxl-2 col-md-3"> |
|
11 |
- <button type="button" class="btn btn-light mb-2"> |
|
12 |
- 전체 <span class="badge bg-secondary text-light">개</span> |
|
13 |
- </button> |
|
14 |
- </div> |
|
15 |
- <!-- End 전체 --> |
|
16 |
- |
|
17 |
- <div class="col-xxl-2 col-md-3"> |
|
18 |
- <button type="button" class="btn btn-light mb-2"> |
|
19 |
- 진행중 프로젝트 <span class="badge bg-secondary text-light">개</span> |
|
20 |
- </button> |
|
21 |
- </div> |
|
22 |
- <div class="col-xxl-2 col-xl-3"> |
|
23 |
- <button type="button" class="btn btn-light mb-2"> |
|
24 |
- 완료된 프로젝트 <span class="badge bg-secondary text-light">개</span> |
|
25 |
- </button> |
|
26 |
- </div> |
|
27 |
- |
|
28 |
- <div class="col-lg-12"> |
|
29 |
- <div class="card"> |
|
30 |
- <div class="card-body"> |
|
31 |
- <h5 class="card-title">프로젝트 내역</h5> |
|
32 |
- <div class="d-flex pb-3 justify-content-between"> |
|
33 |
- |
|
34 |
- <div class="datatable-search d-flex gap-1 "> |
|
35 |
- <div class="col-xxl-5 col-md-4"> |
|
36 |
- <select class="form-select" v-model="selectedYear"> |
|
37 |
- <option value="">전체</option> |
|
38 |
- <option v-for="year in years" :key="year" :value="year">{{ year }}</option> |
|
39 |
- </select> |
|
40 |
- </div> |
|
41 |
- <div class="col-xxl-5 col-md-4"> |
|
42 |
- <select class="form-select" v-model="selectedMonth"> |
|
43 |
- <option value="">월</option> |
|
44 |
- <option v-for="month in months" :key="month" :value="month">{{ month }}</option> |
|
45 |
- </select> |
|
46 |
- </div> |
|
47 |
- <button type="button" class="btn btn-outline-secondary col-xxl-5 col-xl-4" |
|
48 |
- @click="filterData">조회</button> |
|
49 |
- |
|
50 |
- </div> |
|
51 |
- <div class="d-flex justify-content-end "> |
|
52 |
- <button type="button" class="btn btn-outline-primary"> |
|
53 |
- 등록 |
|
54 |
- </button> |
|
55 |
- <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
56 |
- 삭제 |
|
57 |
- </button> |
|
58 |
- </div> |
|
59 |
- |
|
60 |
- </div> |
|
61 |
- <!-- Table --> |
|
62 |
- <table id="myTable" class="table datatable table-hover table-bordered"> |
|
63 |
- <!-- 동적으로 <th> 생성 --> |
|
64 |
- <thead> |
|
65 |
- <tr> |
|
66 |
- <th rowspan="2">No</th> |
|
67 |
- <th rowspan="2">프로젝트명</th> |
|
68 |
- <th rowspan="2">주관</th> |
|
69 |
- <th colspan="4" class="text-center">기간</th> |
|
70 |
- <th rowspan="2">총 투입일</th> |
|
71 |
- <th rowspan="2">비고</th> |
|
72 |
- </tr> |
|
73 |
- <tr> |
|
74 |
- <th data-type="date" data-format="YYYY/DD/MM">사업시작</th> |
|
75 |
- <th data-type="date" data-format="YYYY/DD/MM">사업종료</th> |
|
76 |
- <th data-type="date" data-format="YYYY/DD/MM">투입시작</th> |
|
77 |
- <th data-type="date" data-format="YYYY/DD/MM">투입종료</th> |
|
78 |
- </tr> |
|
79 |
- </thead> |
|
80 |
- <!-- 동적으로 <td> 생성 --> |
|
81 |
- <tbody> |
|
82 |
- <tr v-for="(item, index) in ProjectData" :key="index"> |
|
83 |
- <td> |
|
84 |
- <div class="form-check"> |
|
85 |
- <label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> |
|
86 |
- <input v-model="item.acceptTerms" class="form-check-input" type="checkbox" /> |
|
87 |
- </div> |
|
88 |
- </td> |
|
89 |
- <td>{{ item.projectNm }}</td> |
|
90 |
- <td>{{ item.orgNm }}</td> |
|
91 |
- <td>{{ item.BstartDate }}</td> |
|
92 |
- <td>{{ item.BendDate }}</td> |
|
93 |
- <td>{{ item.MystartDate }}</td> |
|
94 |
- <td>{{ item.MyendDate }}</td> |
|
95 |
- <td>{{ item.totalDays }}</td> |
|
96 |
- <td>{{ item.rm }}</td> |
|
97 |
- </tr> |
|
98 |
- </tbody> |
|
99 |
- </table> |
|
100 |
- <!-- End Table --> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo simple"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
8 |
+ </div> |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
101 | 13 |
</div> |
102 | 14 |
</div> |
103 | 15 |
</div> |
16 |
+ |
|
17 |
+ |
|
18 |
+ <ul class="menu-box danil"> |
|
19 |
+ <router-link to="/userManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
20 |
+ <li><p>사용자권한관리</p> |
|
21 |
+ <div class="icon"><img :src="menuicon" alt=""></div></li> |
|
22 |
+ </router-link> |
|
23 |
+ <router-link to="/accessControlManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
24 |
+ <li> |
|
25 |
+ <p>접근제어관리</p> |
|
26 |
+ <div class="icon"><img :src="menuicon" alt=""></div> |
|
27 |
+ </li> |
|
28 |
+ </router-link> |
|
29 |
+ <router-link to="/commonCodeManagement.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
30 |
+ <li> |
|
31 |
+ <p>공통코드관리</p> |
|
32 |
+ <div class="icon"><img :src="menuicon" alt=""></div> |
|
33 |
+ </li> |
|
34 |
+ </router-link> |
|
35 |
+ |
|
36 |
+ |
|
37 |
+ </ul> |
|
38 |
+ |
|
39 |
+ |
|
104 | 40 |
</div> |
105 |
- </section> |
|
41 |
+ </div> |
|
42 |
+ <!-- End Page Title --> |
|
43 |
+ <div class="content"> |
|
44 |
+ <router-view></router-view> |
|
45 |
+ |
|
46 |
+ </div> |
|
106 | 47 |
</template> |
107 | 48 |
|
108 | 49 |
<script> |
50 |
+import { ref } from 'vue'; |
|
51 |
+ |
|
109 | 52 |
export default { |
110 | 53 |
data() { |
111 | 54 |
return { |
112 |
- years: [2023, 2024, 2025], |
|
113 |
- months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], |
|
55 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
56 |
+ menuicon: "/client/resources/img/menuicon2.png", |
|
57 |
+ topmenuicon: "/client/resources/img/topmenuicon.png", |
|
58 |
+ // 데이터 초기화 |
|
59 |
+ years: [2023, 2024, 2025], // 연도 목록 |
|
60 |
+ months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
114 | 61 |
selectedYear: '', |
115 | 62 |
selectedMonth: '', |
63 |
+ DeptData: [ |
|
64 |
+ { member: '', deptNM: '', acceptTerms: false }, |
|
65 |
+ // 더 많은 데이터 추가... |
|
66 |
+ ], |
|
116 | 67 |
filteredData: [], |
117 | 68 |
}; |
118 | 69 |
}, |
70 |
+ computed: { |
|
71 |
+ }, |
|
119 | 72 |
methods: { |
120 |
- deletePending() { |
|
121 |
- const selectedItems = this.ProjectData.filter(item => item.acceptTerms); |
|
122 |
- const nonDeletableItems = selectedItems.filter(item => item.approvalStatus === true); |
|
123 |
- if (nonDeletableItems.length > 0) { |
|
124 |
- alert("승인된 건은 삭제할 수 없습니다."); |
|
125 |
- return; |
|
126 |
- } |
|
127 |
- if (selectedItems.length > 0) { |
|
128 |
- this.ProjectData = this.ProjectData.filter(item => !item.acceptTerms); |
|
129 |
- alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); |
|
130 |
- } else { |
|
131 |
- alert("선택된 항목이 없습니다."); |
|
132 |
- } |
|
133 |
- }, |
|
134 |
- filterData() { |
|
135 |
- if (!this.selectedYear && !this.selectedMonth) { |
|
136 |
- this.filteredData = this.ProjectData; |
|
137 |
- } else { |
|
138 |
- this.filteredData = this.ProjectData.filter(item => { |
|
139 |
- const itemYear = new Date(item.BstartDate).getFullYear(); |
|
140 |
- const itemMonth = new Date(item.BstartDate).getMonth() + 1; |
|
141 | 73 |
|
142 |
- return ( |
|
143 |
- (!this.selectedYear || itemYear === parseInt(this.selectedYear)) && |
|
144 |
- (!this.selectedMonth || itemMonth === parseInt(this.selectedMonth)) |
|
145 |
- ); |
|
146 |
- }); |
|
147 |
- } |
|
148 |
- this.updateTotalDays(); |
|
149 |
- }, |
|
150 |
- updateTotalDays() { |
|
151 |
- // filteredData에서 각 항목의 totalDays 계산 |
|
152 |
- this.filteredData.forEach(item => { |
|
153 |
- const mystartDate = new Date(item.MystartDate); |
|
154 |
- const myendDate = new Date(item.MyendDate); |
|
155 |
- |
|
156 |
- // 날짜 차이 계산 (밀리초 단위로 계산 후, 일수로 변환) |
|
157 |
- const timeDiff = myendDate - mystartDate; |
|
158 |
- const daysDiff = timeDiff / (1000 * 60 * 60 * 24); |
|
159 |
- item.totalDays = daysDiff >= 0 ? daysDiff : 0; |
|
160 |
- }); |
|
74 |
+ // 페이지 변경 |
|
75 |
+ changePage(page) { |
|
76 |
+ this.currentPage = page; |
|
161 | 77 |
}, |
162 | 78 |
}, |
163 | 79 |
created() { |
164 |
- // 로컬스토리지에서 기존 데이터가 있으면 불러오기 |
|
165 |
- const storedData = localStorage.getItem('projectFormData'); |
|
166 |
- console.log(storedData); |
|
167 |
- if (storedData) { |
|
168 |
- // Parse the data and wrap it in an array |
|
169 |
- const parsedData = JSON.parse(storedData); |
|
170 |
- // Ensure the data is in an array format |
|
171 |
- this.ProjectData = Array.isArray(parsedData) ? parsedData : [parsedData]; |
|
172 |
- } |
|
173 |
-}, |
|
80 |
+ }, |
|
174 | 81 |
mounted() { |
175 |
- const currentYear = new Date().getFullYear(); |
|
176 |
- this.selectedYear = currentYear; |
|
177 |
- this.updateTotalDays(); |
|
82 |
+ |
|
178 | 83 |
}, |
179 | 84 |
}; |
180 | 85 |
</script> |
+++ client/views/pages/Manager/system/userManagement.vue
... | ... | @@ -0,0 +1,176 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body "> | |
4 | + <h2 class="card-title">사용자권한관리</h2> | |
5 | + <div class="flex align-top"> | |
6 | + <div class="sch-form-wrap search"> | |
7 | + | |
8 | + <div class="tbl-wrap table-scroll"> | |
9 | + <table id="myTable" class="tbl data"> | |
10 | + <!-- 동적으로 <th> 생성 --> | |
11 | + <thead> | |
12 | + <tr> | |
13 | + <th>권한목록 </th> | |
14 | + </tr> | |
15 | + </thead> | |
16 | + <!-- 동적으로 <td> 생성 --> | |
17 | + <tbody> | |
18 | + <tr v-for="(item, index) in listData" :key="index"> | |
19 | + <td></td> | |
20 | + </tr> | |
21 | + </tbody> | |
22 | + </table> | |
23 | + | |
24 | + </div> | |
25 | + </div> | |
26 | + | |
27 | + <div style="width: 100%;"> | |
28 | + <div class=" sch-form-wrap title-wrap"> | |
29 | + <h3><img :src="h3icon" alt="">권한 정보</h3> | |
30 | + <div class="buttons" style="margin: 0;"> | |
31 | + <button type="submit" class="btn sm tertiary">신규</button> | |
32 | + <button type="reset" class="btn sm secondary">등록</button> | |
33 | + <button type="delete" class="btn sm btn-red">삭제</button> | |
34 | + </div> | |
35 | + </div> | |
36 | + <form class="row g-3 pt-3 needs-validation detail" @submit.prevent="handleSubmit" | |
37 | + style="margin-bottom: 3rem;"> | |
38 | + <div class="col-12"> | |
39 | + <label for="purpose" class="form-label"> | |
40 | + <p>권한코드 | |
41 | + <p class="require"><img :src="require" alt=""></p> | |
42 | + </p> | |
43 | + </label> | |
44 | + <input type="text" class="form-control" id="purpose" v-model="purpose" /> | |
45 | + </div> | |
46 | + <div class="col-12"> | |
47 | + <label for="purpose" class="form-label"> | |
48 | + <p>권한명 | |
49 | + <p class="require"><img :src="require" alt=""></p> | |
50 | + </p> | |
51 | + </label> | |
52 | + <input type="text" class="form-control" id="purpose" v-model="purpose" /> | |
53 | + </div> | |
54 | + | |
55 | + <div class="col-12 chuljang "> | |
56 | + <label for="prvonsh" class="form-label">권한설명</label> | |
57 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
58 | + </div> | |
59 | + <div class="col-12 border-x input-radio"> | |
60 | + <label for="prvonsh" class="form-label"> <p>사용여부 | |
61 | + <p class="require"><img :src="require" alt=""></p> | |
62 | + </p></label> | |
63 | + <div class="chk-area"> | |
64 | + <div class="form-check"> | |
65 | + <input type="radio" name="rdo_1" id="rdo_1"> | |
66 | + <label for="rdo_1">사용</label> | |
67 | + </div> | |
68 | + <div class="form-check"> | |
69 | + <input type="radio" name="rdo_1" id="rdo_2" checked> | |
70 | + <label for="rdo_2">미사용</label> | |
71 | + </div> | |
72 | + </div> | |
73 | + </div> | |
74 | + | |
75 | + | |
76 | + </form> | |
77 | + </div> | |
78 | + </div> | |
79 | + </div> | |
80 | + | |
81 | + </div> | |
82 | + | |
83 | +</template> | |
84 | + | |
85 | +<script> | |
86 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
87 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
88 | +export default { | |
89 | + data() { | |
90 | + return { | |
91 | + require: "/client/resources/img/require.png", | |
92 | + h3icon: "/client/resources/img/h3icon.png", | |
93 | + photoicon: "/client/resources/img/photo_icon.png", | |
94 | + img1: "/client/resources/img/img.png", | |
95 | + icon1: "/client/resources/img/icon.png", | |
96 | + dateicon: "/client/resources/img/date.png", | |
97 | + startbtn: "/client/resources/img/start.png", | |
98 | + stopbtn: "/client/resources/img/stop.png", | |
99 | + moreicon: "/client/resources/img/more.png", | |
100 | + today: new Date().toLocaleDateString('ko-KR', { | |
101 | + year: 'numeric', | |
102 | + month: '2-digit', | |
103 | + day: '2-digit', | |
104 | + weekday: 'short', | |
105 | + }), | |
106 | + time: this.getCurrentTime(), | |
107 | + listData: Array.from({ length: 20 }, (_, i) => ({ | |
108 | + department: `부서 ${i + 1}`, | |
109 | + name: `이름 ${i + 1}`, | |
110 | + position: `직급 ${i + 1}` | |
111 | + })) | |
112 | + } | |
113 | + }, | |
114 | + components: { | |
115 | + SearchOutlined | |
116 | + }, | |
117 | + methods: { | |
118 | + formatBudget(amount) { | |
119 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
120 | + }, | |
121 | + isPastPeriod(period) { | |
122 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
123 | + const endDateStr = period.split('~')[1]?.trim(); | |
124 | + if (!endDateStr) return false; | |
125 | + | |
126 | + const endDate = new Date(endDateStr); | |
127 | + const today = new Date(); | |
128 | + | |
129 | + // 현재 날짜보다 과거면 true | |
130 | + return endDate < today; | |
131 | + }, | |
132 | + getStatusClass(status) { | |
133 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
134 | + }, | |
135 | + getStatusClass(status) { | |
136 | + if (status === '미진행') return 'status-pending'; | |
137 | + if (status === '진행중') return 'status-approved'; | |
138 | + | |
139 | + // Default empty string | |
140 | + return ''; | |
141 | + }, | |
142 | + getCurrentTime() { | |
143 | + const now = new Date(); | |
144 | + const hours = String(now.getHours()).padStart(2, '0'); | |
145 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
146 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
147 | + return `${hours}:${minutes}:${seconds}`; | |
148 | + }, | |
149 | + getCategoryClass(category) { | |
150 | + switch (category) { | |
151 | + case '용역': return 'category-service'; | |
152 | + case '내부': return 'category-internal'; | |
153 | + case '국가과제': return 'category-government'; | |
154 | + default: return ''; | |
155 | + } | |
156 | + }, | |
157 | + }, | |
158 | + watch: { | |
159 | + | |
160 | + }, | |
161 | + computed: { | |
162 | + | |
163 | + }, | |
164 | + mounted() { | |
165 | + console.log('main mounted'); | |
166 | + setInterval(() => { | |
167 | + this.time = this.getCurrentTime(); | |
168 | + }, 1000); | |
169 | + } | |
170 | +} | |
171 | +</script> | |
172 | +<style scoped> | |
173 | +tr { | |
174 | + cursor: pointer; | |
175 | +} | |
176 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/task/meetingDetail.vue
... | ... | @@ -0,0 +1,163 @@ |
1 | +<template> | |
2 | + <div class="card"> | |
3 | + | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">회의록 등록</h2> | |
6 | + <p class="require"><img :src="require" alt=""> 필수입력</p> | |
7 | + <!-- Multi Columns Form --> | |
8 | + <form class="row g-3 needs-validation detail" @submit.prevent="handleSubmit"> | |
9 | + <div class="col-12"> | |
10 | + <label for="inputName5" class="form-label"><p>프로젝트명<p class="require"><img :src="require" alt=""></p></p></label> | |
11 | + <input type="text" class="form-control" readonly/> | |
12 | + </div> | |
13 | + <div class="col-12"> | |
14 | + <label for="inputName5" class="form-label"><p>회의록 제목<p class="require"><img :src="require" alt=""></p></p></label> | |
15 | + <input type="text" class="form-control" readonly /> | |
16 | + </div> | |
17 | + | |
18 | + <div class="col-12"> | |
19 | + <label for="startDate" class="form-label"><p>일시<p class="require"><img :src="require" alt=""></p></p></label> | |
20 | + <input type="text" class="form-control" readonly /> | |
21 | + </div> | |
22 | + | |
23 | + <div class="col-12"> | |
24 | + <label for="endDate" class="form-label"><p>장소<p class="require"><img :src="require" alt=""></p></p></label> | |
25 | + <input type="text" class="form-control" readonly/> | |
26 | + </div> | |
27 | + | |
28 | + <div class="col-12 "> | |
29 | + <label for="dayCount" class="form-label">참석자</label> | |
30 | + <div class="tbl-wrap"> | |
31 | + <table id="myTable" class="tbl data"> | |
32 | + <thead> | |
33 | + <tr> | |
34 | + <th>소속</th> | |
35 | + <th>직위</th> | |
36 | + <th>이름</th> | |
37 | + </tr> | |
38 | + </thead> | |
39 | + <!-- 동적으로 <td> 생성 --> | |
40 | + <tbody> | |
41 | + <tr v-for="(member, index) in members" :key="index"> | |
42 | + <td><input type="text" class="form-control" v-model="member.department" /></td> | |
43 | + <td><input type="text" class="form-control" v-model="member.position" /></td> | |
44 | + <td><input type="text" class="form-control" v-model="member.name" /></td> | |
45 | + | |
46 | + </tr> | |
47 | + | |
48 | + </tbody> | |
49 | + </table> | |
50 | + | |
51 | + </div> | |
52 | + </div> | |
53 | + | |
54 | + | |
55 | + <div class="col-12 chuljang"> | |
56 | + <label for="prvonsh" class="form-label">회의내용</label> | |
57 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" readonly/> | |
58 | + </div> | |
59 | + <div class="col-12 chuljang"> | |
60 | + <label for="prvonsh" class="form-label">비고</label> | |
61 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" readonly/> | |
62 | + </div> | |
63 | + | |
64 | + <div class="col-12 border-x"> | |
65 | + <label for="member" class="form-label"> | |
66 | + 회의비 | |
67 | + <button type="button" title="추가" @click="addReceipt"> | |
68 | + <PlusCircleFilled /> | |
69 | + </button> | |
70 | + </label> | |
71 | + | |
72 | + <div class="approval-container"> | |
73 | + <div v-for="(receipt, index) in receipts" :key="index" class="d-flex gap-2 addapproval mb-2"> | |
74 | + <input type="text" class="form-control" v-model="receipt.name" style="max-width: 150px;" readonly/> | |
75 | + <input type="text" class="form-control" v-model="receipt.cost" style="max-width: 150px;" readonly/> | |
76 | + | |
77 | + | |
78 | + <!-- 선택된 파일 이름 표시 --> | |
79 | + <span class="file-name">영수증</span> | |
80 | + <button type="button" @click="download(index)" class="download-button"> | |
81 | + <VerticalAlignBottomOutlined /> | |
82 | + </button> | |
83 | + </div> | |
84 | + </div> | |
85 | + </div> | |
86 | + </form><!-- End Multi Columns Form --> | |
87 | + <div class="buttons"> | |
88 | + <button type="submit" class="btn secondary">수정</button> | |
89 | + <button type="submit" class="btn btn-red">삭제</button> | |
90 | + <button type="reset" class="btn tertiary">목록</button> | |
91 | + </div> | |
92 | + </div> | |
93 | + </div> | |
94 | +</template> | |
95 | + | |
96 | +<script> | |
97 | +import { PlusCircleFilled, VerticalAlignBottomOutlined } from '@ant-design/icons-vue'; | |
98 | +import HrPopup from '../../../component/Popup/HrPopup.vue'; | |
99 | +export default { | |
100 | + data() { | |
101 | + const today = new Date().toISOString().split('T')[0]; | |
102 | + return { | |
103 | + showPopup: false, | |
104 | + receipts: [ | |
105 | + { | |
106 | + name: '', | |
107 | + cost: '', | |
108 | + }, | |
109 | + ], | |
110 | + members: [] , | |
111 | + approvals: [], | |
112 | + require: "/client/resources/img/require.png", | |
113 | + startDate: today, | |
114 | + startTime: "09:00", // 기본 시작 시간 09:00 | |
115 | + endDate: today, | |
116 | + endTime: "18:00", // 기본 종료 시간 18:00 | |
117 | + category: "", | |
118 | + dayCount: 1, | |
119 | + reason: "", // 사유 | |
120 | + fileName: '', | |
121 | + }; | |
122 | + }, | |
123 | + components: { | |
124 | + PlusCircleFilled, VerticalAlignBottomOutlined, HrPopup | |
125 | + }, | |
126 | + computed: { | |
127 | + }, | |
128 | + methods: { | |
129 | + calculateDayCount() { | |
130 | + const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
131 | + const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
132 | + | |
133 | + let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
134 | + | |
135 | + if (this.startDate !== this.endDate) { | |
136 | + // 시작일과 종료일이 다른경우 | |
137 | + const startDateObj = new Date(this.startDate); | |
138 | + const endDateObj = new Date(this.endDate); | |
139 | + const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
140 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
141 | + this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
142 | + } else { | |
143 | + this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
144 | + } | |
145 | + } else { | |
146 | + // 시작일과 종료일이 같은 경우 | |
147 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
148 | + this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
149 | + } else { | |
150 | + this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
151 | + } | |
152 | + } | |
153 | + }, | |
154 | + | |
155 | + | |
156 | + }, | |
157 | + mounted() { | |
158 | + }, | |
159 | +}; | |
160 | +</script> | |
161 | +<style scoped> | |
162 | +table th, table td{border: 0; border-bottom: 0 !important;} | |
163 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/task/meetingInsert.vue
... | ... | @@ -0,0 +1,229 @@ |
1 | +<template> | |
2 | + <div class="card"> | |
3 | + | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">회의록 등록</h2> | |
6 | + <p class="require"><img :src="require" alt=""> 필수입력</p> | |
7 | + <!-- Multi Columns Form --> | |
8 | + <form class="row g-3 needs-validation" @submit.prevent="handleSubmit"> | |
9 | + <div class="col-12"> | |
10 | + <label for="inputName5" class="form-label"><p>프로젝트명<p class="require"><img :src="require" alt=""></p></p></label> | |
11 | + <input type="text" class="form-control" /> | |
12 | + </div> | |
13 | + <div class="col-12"> | |
14 | + <label for="inputName5" class="form-label"><p>회의록 제목<p class="require"><img :src="require" alt=""></p></p></label> | |
15 | + <input type="text" class="form-control" /> | |
16 | + </div> | |
17 | + | |
18 | + <div class="col-12"> | |
19 | + <label for="startDate" class="form-label"><p>일시<p class="require"><img :src="require" alt=""></p></p></label> | |
20 | + <div class="d-flex gap-1"> | |
21 | + <input type="date" class="form-control" id="startDate" v-model="startDate" /> | |
22 | + <input type="time" class="form-control" id="startTime" v-model="startTime" /> | |
23 | + ~ | |
24 | +</div> | |
25 | + | |
26 | +<div class="d-flex gap-1"> | |
27 | + <input type="date" class="form-control" id="endDate" v-model="endDate" /> | |
28 | + <input type="time" class="form-control" id="endTime" v-model="endTime" /> | |
29 | +</div> | |
30 | + </div> | |
31 | + | |
32 | + <div class="col-12"> | |
33 | + <label for="endDate" class="form-label"><p>장소<p class="require"><img :src="require" alt=""></p></p></label> | |
34 | + <input type="text" class="form-control" /> | |
35 | + </div> | |
36 | + | |
37 | + <div class="col-12 "> | |
38 | + <label for="dayCount" class="form-label">참석자<button type="button" title="추가" @click="showPopup = true"> | |
39 | + <PlusCircleFilled /> | |
40 | + </button></label> | |
41 | + <HrPopup v-if="showPopup" @close="showPopup = false" @select="addMember"/> | |
42 | + <div class="tbl-wrap"> | |
43 | + <table id="myTable" class="tbl data"> | |
44 | + <thead> | |
45 | + <tr> | |
46 | + <th>소속</th> | |
47 | + <th>직위</th> | |
48 | + <th>이름</th> | |
49 | + <th>삭제</th> | |
50 | + </tr> | |
51 | + </thead> | |
52 | + <!-- 동적으로 <td> 생성 --> | |
53 | + <tbody> | |
54 | + <tr v-for="(member, index) in members" :key="index"> | |
55 | + <td><input type="text" class="form-control" v-model="member.department" /></td> | |
56 | + <td><input type="text" class="form-control" v-model="member.position" /></td> | |
57 | + <td><input type="text" class="form-control" v-model="member.name" /></td> | |
58 | + <td> | |
59 | + <button type="button" @click="removeMember(index)"> | |
60 | + <CloseOutlined /> | |
61 | + </button> | |
62 | + </td> | |
63 | + </tr> | |
64 | + | |
65 | + </tbody> | |
66 | + </table> | |
67 | + | |
68 | + </div> | |
69 | + </div> | |
70 | + | |
71 | + | |
72 | + <div class="col-12 chuljang"> | |
73 | + <label for="prvonsh" class="form-label">회의내용</label> | |
74 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
75 | + </div> | |
76 | + <div class="col-12 chuljang"> | |
77 | + <label for="prvonsh" class="form-label">비고</label> | |
78 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
79 | + </div> | |
80 | + | |
81 | + <div class="col-12 border-x"> | |
82 | + <label for="member" class="form-label"> | |
83 | + 회의비 | |
84 | + <button type="button" title="추가" @click="addReceipt"> | |
85 | + <PlusCircleFilled /> | |
86 | + </button> | |
87 | + </label> | |
88 | + | |
89 | + <div class="approval-container"> | |
90 | + <div v-for="(receipt, index) in receipts" :key="index" class="d-flex gap-2 addapproval mb-2"> | |
91 | + <input type="text" class="form-control" v-model="receipt.name" style="max-width: 150px;" placeholder="여비내역입력"/> | |
92 | + <input type="text" class="form-control" v-model="receipt.cost" style="max-width: 150px;" placeholder="금액입력"/> | |
93 | + | |
94 | + <!-- 커스텀 업로드 버튼 --> | |
95 | + <label :for="'fileUpload-' + index" class="upload-label"> | |
96 | + 영수증 첨부 | |
97 | + </label> | |
98 | + | |
99 | + <!-- 실제 파일 input (숨김 처리) --> | |
100 | + <input | |
101 | + :id="'fileUpload-' + index" | |
102 | + type="file" | |
103 | + @change="handleFileUpload(index, $event)" | |
104 | + class="hidden-file-input" | |
105 | + /> | |
106 | + <!-- 선택된 파일 이름 표시 --> | |
107 | + <span v-if="receipt.fileName" class="file-name">{{ receipt.fileName }}</span> | |
108 | + <button type="button" @click="removeReceipt(index)" class="delete-button"> | |
109 | + <CloseOutlined /> | |
110 | + </button> | |
111 | + </div> | |
112 | + </div> | |
113 | + </div> | |
114 | + </form><!-- End Multi Columns Form --> | |
115 | + <div class="buttons"> | |
116 | + <button type="submit" class="btn primary">등록</button> | |
117 | + <button type="reset" class="btn tertiary">취소</button> | |
118 | + </div> | |
119 | + </div> | |
120 | + </div> | |
121 | +</template> | |
122 | + | |
123 | +<script> | |
124 | +import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; | |
125 | +import HrPopup from '../../../component/Popup/HrPopup.vue'; | |
126 | +export default { | |
127 | + data() { | |
128 | + const today = new Date().toISOString().split('T')[0]; | |
129 | + return { | |
130 | + showPopup: false, | |
131 | + receipts: [ | |
132 | + { | |
133 | + name: '', | |
134 | + cost: '', | |
135 | + }, | |
136 | + ], | |
137 | + members: [] , | |
138 | + approvals: [], | |
139 | + require: "/client/resources/img/require.png", | |
140 | + startDate: today, | |
141 | + startTime: "09:00", // 기본 시작 시간 09:00 | |
142 | + endDate: today, | |
143 | + endTime: "18:00", // 기본 종료 시간 18:00 | |
144 | + category: "", | |
145 | + dayCount: 1, | |
146 | + reason: "", // 사유 | |
147 | + fileName: '', | |
148 | + }; | |
149 | + }, | |
150 | + components: { | |
151 | + PlusCircleFilled, CloseOutlined, HrPopup | |
152 | + }, | |
153 | + computed: { | |
154 | + }, | |
155 | + methods: { | |
156 | + handleFileUpload(index, event) { | |
157 | + const file = event.target.files[0]; | |
158 | + if (file) { | |
159 | + this.receipts[index].file = file; | |
160 | + this.receipts[index].fileName = file.name; | |
161 | + } | |
162 | + }, | |
163 | + addReceipt() { | |
164 | + this.receipts.push({ | |
165 | + name: '', | |
166 | + cost: '', | |
167 | + file: null, | |
168 | + }); | |
169 | +}, | |
170 | +removeReceipt(index) { | |
171 | + this.receipts.splice(index, 1); | |
172 | +}, | |
173 | +addMember(selectedUser) { | |
174 | + this.members.push({ | |
175 | + department: selectedUser.department, | |
176 | + position: selectedUser.position, | |
177 | + name: selectedUser.name, // or other fields if needed | |
178 | + }); | |
179 | + this.showPopup = false; // 팝업 닫기 | |
180 | + }, | |
181 | + removeMember(index) { | |
182 | + this.members.splice(index, 1); | |
183 | + }, | |
184 | + addApproval(selectedUser) { | |
185 | + this.approvals.push({ | |
186 | + category: '결재', | |
187 | + name: selectedUser.name, // or other fields if needed | |
188 | + }); | |
189 | + this.showPopup = false; // 팝업 닫기 | |
190 | + }, | |
191 | + // 승인자 삭제 | |
192 | + removeApproval(index) { | |
193 | + this.approvals.splice(index, 1); | |
194 | + }, | |
195 | + calculateDayCount() { | |
196 | + const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
197 | + const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
198 | + | |
199 | + let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
200 | + | |
201 | + if (this.startDate !== this.endDate) { | |
202 | + // 시작일과 종료일이 다른경우 | |
203 | + const startDateObj = new Date(this.startDate); | |
204 | + const endDateObj = new Date(this.endDate); | |
205 | + const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
206 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
207 | + this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
208 | + } else { | |
209 | + this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
210 | + } | |
211 | + } else { | |
212 | + // 시작일과 종료일이 같은 경우 | |
213 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
214 | + this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
215 | + } else { | |
216 | + this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
217 | + } | |
218 | + } | |
219 | + }, | |
220 | + | |
221 | + | |
222 | + }, | |
223 | + mounted() { | |
224 | + }, | |
225 | +}; | |
226 | +</script> | |
227 | +<style scoped> | |
228 | +table th, table td{border: 0; border-bottom: 0 !important;} | |
229 | +</style>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/Manager/task/projectDetail.vue
... | ... | @@ -0,0 +1,304 @@ |
1 | +<template> | |
2 | + <div class="card"> | |
3 | + <div class="card-body"> | |
4 | + <h2 class="card-title">프로젝트 현황</h2> | |
5 | + <!-- Multi Columns Form --> | |
6 | + <div class=" sch-form-wrap title-wrap"><h3><img :src="h3icon" alt="">프로젝트 정보</h3></div> | |
7 | + <form class="row g-3 pt-3 needs-validation detail" @submit.prevent="handleSubmit" style="margin-bottom: 3rem;"> | |
8 | + | |
9 | + <div class="col-12"> | |
10 | + <label for="where" class="form-label">구분</label> | |
11 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
12 | + </div> | |
13 | + <div class="col-12"> | |
14 | + <label for="where" class="form-label">제목</label> | |
15 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
16 | + </div> | |
17 | + <div class="col-12"> | |
18 | + <div class="col-12 border-x"> | |
19 | + <label for="where" class="form-label">주관기관</label> | |
20 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
21 | + </div> | |
22 | + <div class="col-12 border-x"> | |
23 | + <label for="where" class="form-label">담당부서</label> | |
24 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
25 | + </div> | |
26 | + | |
27 | + </div> | |
28 | + <div class="col-12"> | |
29 | + <div class="col-12 border-x"> | |
30 | + <label for="where" class="form-label">프로젝트 책임자</label> | |
31 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
32 | + </div> | |
33 | + <div class="col-12 border-x"> | |
34 | + <label for="where" class="form-label">사업비</label> | |
35 | + <input type="text" class="form-control" id="where" v-model="where" readonly/> | |
36 | + </div> | |
37 | + | |
38 | + </div> | |
39 | + <div class="col-12"> | |
40 | + <label for="purpose" class="form-label">사업기간</label> | |
41 | + <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> | |
42 | + </div> | |
43 | + <div class="col-12"> | |
44 | + <label for="purpose" class="form-label">계획기간</label> | |
45 | + <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> | |
46 | + </div> | |
47 | + <div class="col-12"> | |
48 | + <label for="purpose" class="form-label">실제기간</label> | |
49 | + <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> | |
50 | + </div> | |
51 | + <div class="col-12"> | |
52 | + <label for="purpose" class="form-label">진행상태</label> | |
53 | + <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> | |
54 | + </div> | |
55 | + <div class="col-12"> | |
56 | + <label for="purpose" class="form-label">투입인력</label> | |
57 | + <input type="text" class="form-control" id="purpose" v-model="purpose" readonly/> | |
58 | + </div> | |
59 | + | |
60 | + <div class="col-12 chuljang border-x"> | |
61 | + <label for="prvonsh" class="form-label">비고</label> | |
62 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" readonly/> | |
63 | + </div> | |
64 | + | |
65 | + | |
66 | + </form> | |
67 | + <div class="buttons"> | |
68 | + <button type="reset" class="btn secondary">수정</button> | |
69 | + <button type="delete" class="btn btn-red">삭제</button> | |
70 | + <button type="submit" class="btn tertiary">목록</button> | |
71 | + </div> | |
72 | + <div class=" sch-form-wrap title-wrap"><h3><img :src="h3icon" alt="">회의록 정보</h3></div> | |
73 | + <div class="cost-statue"> | |
74 | + <p class="costtitle">회의비 사용내역</p> | |
75 | + <div class="d-flex"> | |
76 | + <div class="col-12 border-x "> | |
77 | + <p style="color: #219A8C;">전체</p> | |
78 | + <input type="text" class="form-control"> | |
79 | + </div> | |
80 | + <div class="col-12 border-x"> | |
81 | + <p style="color: #1D75E1;">사용</p> | |
82 | + <input type="text" class="form-control"> | |
83 | + </div> | |
84 | + <div class="col-12 border-x"> | |
85 | + <p style="color: #E92727;">미사용</p> | |
86 | + <input type="text" class="form-control"> | |
87 | + </div> | |
88 | + </div> | |
89 | + </div> | |
90 | + <div class="tbl-wrap"> | |
91 | + <table id="myTable" class="tbl data"> | |
92 | + <colgroup> | |
93 | + <col style="width: 200px;"> | |
94 | + <col style=" width: "> | |
95 | + </colgroup> | |
96 | + <thead> | |
97 | + <tr> | |
98 | + <th>제목</th> | |
99 | + <th>참석자</th> | |
100 | + <th>회의비</th> | |
101 | + <th>일시</th> | |
102 | + </tr> | |
103 | + </thead> | |
104 | + <!-- 동적으로 <td> 생성 --> | |
105 | + <tbody> | |
106 | + <tr v-for="(item, index) in listData" :key="index" @click="goToPage('회의록 상세')"> | |
107 | + <td>{{ item.title }}</td> | |
108 | + <td>{{ item.member }}</td> | |
109 | + <td>{{ item.cost }}</td> | |
110 | + <td>{{ item.period }}</td> | |
111 | + </tr> | |
112 | + </tbody> | |
113 | + </table> | |
114 | + | |
115 | + </div> | |
116 | + <div class="pagination"> | |
117 | + <ul> | |
118 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
119 | + <li class="arrow" :class="{ disabled: currentPage === 1 }" @click="changePage(currentPage - 1)"> | |
120 | + < | |
121 | + </li> | |
122 | + | |
123 | + <!-- 페이지 번호 --> | |
124 | + <li v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }" | |
125 | + @click="changePage(page)"> | |
126 | + {{ page }} | |
127 | + </li> | |
128 | + | |
129 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
130 | + <li class="arrow" :class="{ disabled: currentPage === totalPages }" @click="changePage(currentPage + 1)"> | |
131 | + > | |
132 | + </li> | |
133 | + </ul> | |
134 | + </div> | |
135 | + <div class="buttons"> | |
136 | + <button type="insert" class="btn primary sm" @click="goToPage('회의록 등록')">회의록 등록</button> | |
137 | + </div> | |
138 | + | |
139 | + </div> | |
140 | + </div> | |
141 | +</template> | |
142 | + | |
143 | +<script> | |
144 | +import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; | |
145 | + | |
146 | +export default { | |
147 | + data() { | |
148 | + const today = new Date().toISOString().split('T')[0]; | |
149 | + return { | |
150 | + currentPage: 1, | |
151 | + totalPages: 3, | |
152 | + listData: [ | |
153 | + { | |
154 | + title: '프로젝트 회의록', | |
155 | + member: 'ooo외 3명', | |
156 | + cost: '100,000', | |
157 | + period: '2025-01-01 14:00 ~ 2025-01-01 16:00', | |
158 | + }, ], | |
159 | + h3icon: "/client/resources/img/h3icon.png", | |
160 | + fileName: '', | |
161 | + startDate: today, | |
162 | + startTime: '09:00', | |
163 | + endDate: today, | |
164 | + endTime: '18:00', | |
165 | + where: '', | |
166 | + purpose: '', | |
167 | + approvals: [ | |
168 | + { | |
169 | + category: '결재', | |
170 | + name: '', | |
171 | + }, | |
172 | + ], | |
173 | + receipts: [ | |
174 | + { | |
175 | + type: '개인결제', | |
176 | + category: '결재', | |
177 | + category1: '구분', | |
178 | + }, | |
179 | + ], | |
180 | + }; | |
181 | + }, | |
182 | + components: { | |
183 | + PlusCircleFilled, CloseOutlined | |
184 | + }, | |
185 | + computed: { | |
186 | + loginUser() { | |
187 | + const authStore = useAuthStore(); | |
188 | + return authStore.getLoginUser; | |
189 | + }, | |
190 | + }, | |
191 | + | |
192 | + methods: { | |
193 | + goToPage(type) { | |
194 | + if (type === '회의록 등록') { | |
195 | + this.$router.push('/meetingInsert.page'); | |
196 | + } else if (type === '회의록 상세') { | |
197 | + this.$router.push('/meetingDetail.page'); | |
198 | + } | |
199 | +}, | |
200 | + handleFileUpload(event) { | |
201 | + const file = event.target.files[0]; | |
202 | + if (file) { | |
203 | + this.fileName = file.name; | |
204 | + } | |
205 | + }, | |
206 | + addApproval() { | |
207 | + this.approvals.push({ | |
208 | + category: '결재', | |
209 | + name: '', | |
210 | + }); | |
211 | + }, | |
212 | + addReceipt() { | |
213 | + this.receipts.push({ | |
214 | + type: '개인결제', | |
215 | + category: '결재', | |
216 | + category1: '', | |
217 | + name: '', | |
218 | + file: null, | |
219 | + }); | |
220 | +}, | |
221 | + // 승인자 삭제 | |
222 | + removeApproval(index) { | |
223 | + this.approvals.splice(index, 1); | |
224 | + }, | |
225 | + removeReceipt(index) { | |
226 | + this.receipts.splice(index, 1); | |
227 | +}, | |
228 | + validateForm() { | |
229 | + // 필수 입력 필드 체크 | |
230 | + if ( | |
231 | + this.startDate && | |
232 | + this.startTime && | |
233 | + this.endDate && | |
234 | + this.endTime && | |
235 | + this.where && | |
236 | + this.purpose.trim() !== "" | |
237 | + ) { | |
238 | + this.isFormValid = true; | |
239 | + } else { | |
240 | + this.isFormValid = false; | |
241 | + } | |
242 | + }, | |
243 | + calculateDayCount() { | |
244 | + const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
245 | + const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
246 | + | |
247 | + let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
248 | + | |
249 | + if (this.startDate !== this.endDate) { | |
250 | + // 시작일과 종료일이 다른경우 | |
251 | + const startDateObj = new Date(this.startDate); | |
252 | + const endDateObj = new Date(this.endDate); | |
253 | + const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
254 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
255 | + this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
256 | + } else { | |
257 | + this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
258 | + } | |
259 | + } else { | |
260 | + // 시작일과 종료일이 같은 경우 | |
261 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
262 | + this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
263 | + } else { | |
264 | + this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
265 | + } | |
266 | + } | |
267 | + | |
268 | + this.validateForm(); // dayCount 변경 후 폼 재검증 | |
269 | + }, | |
270 | + handleSubmit() { | |
271 | + this.validateForm(); // 제출 시 유효성 확인 | |
272 | + if (this.isFormValid) { | |
273 | + localStorage.setItem('ChuljangFormData', JSON.stringify(this.$data)); | |
274 | + alert("승인 요청이 완료되었습니다."); | |
275 | + // 추가 처리 로직 (API 요청 등) | |
276 | + } else { | |
277 | + alert("모든 필드를 올바르게 작성해주세요."); | |
278 | + } | |
279 | + }, | |
280 | + loadFormData() { | |
281 | + const savedData = localStorage.getItem('ChuljangFormData'); | |
282 | + if (savedData) { | |
283 | + this.$data = JSON.parse(savedData); | |
284 | + } | |
285 | + }, | |
286 | + }, | |
287 | + mounted() { | |
288 | + // Load the saved form data when the page is loaded | |
289 | + this.loadFormData(); | |
290 | + }, | |
291 | + watch: { | |
292 | + startDate: 'calculateDayCount', | |
293 | + startTime: 'calculateDayCount', | |
294 | + endDate: 'calculateDayCount', | |
295 | + endTime: 'calculateDayCount', | |
296 | + where: 'validateForm', | |
297 | + purpose: "validateForm", | |
298 | + }, | |
299 | +}; | |
300 | +</script> | |
301 | + | |
302 | +<style scoped> | |
303 | +tr{cursor: pointer;} | |
304 | +</style> |
+++ client/views/pages/Manager/task/projectInsert.vue
... | ... | @@ -0,0 +1,245 @@ |
1 | +<template> | |
2 | + <div class="card"> | |
3 | + | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">프로젝트 등록</h2> | |
6 | + <p class="require"><img :src="require" alt=""> 필수입력</p> | |
7 | + <form class="row g-3 needs-validation" @submit.prevent="handleSubmit"> | |
8 | + | |
9 | + <div class="col-12"> | |
10 | + <label for="purpose" class="form-label"> | |
11 | + <p>구분 | |
12 | + <p class="require"><img :src="require" alt=""></p> | |
13 | + </p> | |
14 | + </label> | |
15 | + <select class="form-select" style="width: 110px;"> | |
16 | + <option value=""></option> | |
17 | + <option value=""></option> | |
18 | + <option value=""></option> | |
19 | + </select> | |
20 | + </div> | |
21 | + <div class="col-12"> | |
22 | + <label for="inputName5" class="form-label"> | |
23 | + <p>프로젝트명 | |
24 | + <p class="require"><img :src="require" alt=""></p> | |
25 | + </p> | |
26 | + </label> | |
27 | + <input type="text" class="form-control" /> | |
28 | + </div> | |
29 | + <div class="col-12"> | |
30 | + <label for="inputName5" class="form-label">주관기관</label> | |
31 | + <input type="text" class="form-control" /> | |
32 | + </div> | |
33 | + <div class="col-12"> | |
34 | + <div class="col-12 border-x"> | |
35 | + <label for="where" class="form-label"> | |
36 | + <p>담당부서 | |
37 | + <p class="require"><img :src="require" alt=""></p> | |
38 | + </p> | |
39 | + </label> | |
40 | + <input type="text" class="form-control" id="buseo" v-model="selectedDepartment" readonly /> | |
41 | + <input type="button" class="form-control " value="검색" @click="showPopup = true" /> | |
42 | + <BuseoPopup v-if="showPopup" @close="showPopup = false" @select="addBuseo" /> | |
43 | + </div> | |
44 | + <div class="col-12 border-x"> | |
45 | + <label for="where" class="form-label"> | |
46 | + <p>프로젝트 책임자 | |
47 | + <p class="require"><img :src="require" alt=""></p> | |
48 | + </p> | |
49 | + </label> | |
50 | + <input type="text" class="form-control" id="where" v-model="selectedname" readonly /> | |
51 | + <input type="button" class="form-control " value="검색" @click="showPopup1 = true" /> | |
52 | + <HrPopup v-if="showPopup1" @close="showPopup1 = false" @select="addApproval" /> | |
53 | + </div> | |
54 | + | |
55 | + </div> | |
56 | + <div class="col-12"> | |
57 | + <div class="col-12 border-x"> | |
58 | + <label for="where" class="form-label">사업비</label> | |
59 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
60 | + </div> | |
61 | + <div class="col-12 border-x"> | |
62 | + <label for="where" class="form-label">회의비</label> | |
63 | + <input type="text" class="form-control" id="where" v-model="where" /> | |
64 | + </div> | |
65 | + | |
66 | + </div> | |
67 | + <div class="col-12"> | |
68 | + <label for="startDate" class="form-label"> | |
69 | + <p>사업기간 | |
70 | + <p class="require"><img :src="require" alt=""></p> | |
71 | + </p> | |
72 | + </label> | |
73 | + <div class="d-flex gap-1"> | |
74 | + <input type="date" class="form-control" id="startDate" v-model="startDate" /> | |
75 | + <!-- 시간 선택을 위한 select 사용 --> | |
76 | + ~ | |
77 | + </div> | |
78 | + <div class="d-flex gap-1"> | |
79 | + <input type="date" class="form-control" id="endDate" v-model="endDate" /> | |
80 | + </div> | |
81 | + </div> | |
82 | + <div class="col-12"> | |
83 | + <label for="startDate" class="form-label">계획기간</label> | |
84 | + <div class="d-flex gap-1"> | |
85 | + <input type="date" class="form-control" id="startDate" v-model="startDate" /> | |
86 | + <!-- 시간 선택을 위한 select 사용 --> | |
87 | + ~ | |
88 | + </div> | |
89 | + <div class="d-flex gap-1"> | |
90 | + <input type="date" class="form-control" id="endDate" v-model="endDate" /> | |
91 | + </div> | |
92 | + </div> | |
93 | + <div class="col-12"> | |
94 | + <label for="startDate" class="form-label">실제기간</label> | |
95 | + <div class="d-flex gap-1"> | |
96 | + <input type="date" class="form-control" id="startDate" v-model="startDate" /> | |
97 | + <!-- 시간 선택을 위한 select 사용 --> | |
98 | + ~ | |
99 | + </div> | |
100 | + <div class="d-flex gap-1"> | |
101 | + <input type="date" class="form-control" id="endDate" v-model="endDate" /> | |
102 | + </div> | |
103 | + </div> | |
104 | + | |
105 | + | |
106 | + <div class="col-12 "> | |
107 | + <label for="member" class="form-label"> | |
108 | + <p>투입인력 <button type="button" title="추가" @click="showPopup2 = true"> | |
109 | + <PlusCircleFilled /> | |
110 | + </button> | |
111 | + <p class="require"><img :src="require" alt=""></p> | |
112 | + </p> | |
113 | + | |
114 | + </label> | |
115 | + <HrPopup v-if="showPopup2" @close="showPopup2 = false" @select="addApproval1"/> | |
116 | + <div class="approval-container"> | |
117 | + <div v-for="(member, index) in approvals1" :key="index" class="d-flex gap-2 addapproval mb-2"> | |
118 | + <select class="form-select" style="width: 110px;" v-model="member.category"> | |
119 | + <option value="선택">선택</option> | |
120 | + <option value=""></option> | |
121 | + <option value=""></option> | |
122 | + </select> | |
123 | + <input type="text" class="form-control" v-model="member.name" style="max-width: 150px;" /> | |
124 | + | |
125 | + | |
126 | + <button type="button" @click="removeApproval(index)" class="delete-button"> | |
127 | + <CloseOutlined /> | |
128 | + </button> | |
129 | + </div> | |
130 | + </div> | |
131 | + </div> | |
132 | + | |
133 | + <div class="col-12 chuljang border-x"> | |
134 | + <label for="prvonsh" class="form-label">비고</label> | |
135 | + <input type="text" class="form-control textarea" id="reason" v-model="reason" /> | |
136 | + </div> | |
137 | + | |
138 | + | |
139 | + </form> | |
140 | + <div class="buttons"> | |
141 | + <button type="submit" class="btn primary">등록</button> | |
142 | + <button type="reset" class="btn tertiary">취소</button> | |
143 | + </div> | |
144 | + </div> | |
145 | + </div> | |
146 | +</template> | |
147 | + | |
148 | +<script> | |
149 | +import { PlusCircleFilled, CloseOutlined } from '@ant-design/icons-vue'; | |
150 | +import HrPopup from "../../../component/Popup/HrPopup.vue"; | |
151 | +import BuseoPopup from "../../../component/Popup/BuseoPopup.vue"; | |
152 | +export default { | |
153 | + data() { | |
154 | + const today = new Date().toISOString().split('T')[0]; | |
155 | + return { | |
156 | + showPopup: false, | |
157 | + showPopup1: false, | |
158 | + showPopup2: false, | |
159 | + buseos: [], | |
160 | + selectedDepartment: '', | |
161 | + selectedname: '', | |
162 | + approvals: [], | |
163 | + approvals1: [], | |
164 | + require: "/client/resources/img/require.png", | |
165 | + startDate: today, | |
166 | + startTime: "09:00", // 기본 시작 시간 09:00 | |
167 | + endDate: today, | |
168 | + endTime: "18:00", // 기본 종료 시간 18:00 | |
169 | + category: "", | |
170 | + dayCount: 1, | |
171 | + reason: "", // 사유 | |
172 | + }; | |
173 | + }, | |
174 | + components: { | |
175 | + PlusCircleFilled, CloseOutlined, BuseoPopup, HrPopup | |
176 | + }, | |
177 | + computed: { | |
178 | + }, | |
179 | + methods: { | |
180 | + addBuseo(selectedUser) { | |
181 | + this.buseos.push({ | |
182 | + department: selectedUser.department, | |
183 | + name: selectedUser.name | |
184 | + }); | |
185 | + | |
186 | + this.selectedDepartment = selectedUser.department; // 입력창에 표시 | |
187 | + this.showPopup = false; | |
188 | + }, | |
189 | + removeBuseo(index) { | |
190 | + this.buseos.splice(index, 1); | |
191 | + }, | |
192 | + addApproval(selectedUser) { | |
193 | + this.approvals.push({ | |
194 | + name: selectedUser.name | |
195 | + }); | |
196 | + | |
197 | + this.selectedname = selectedUser.name; // 입력창에 표시 | |
198 | + this.showPopup1 = false; | |
199 | + }, | |
200 | + addApproval1(selectedUser) { | |
201 | + this.approvals1.push({ | |
202 | + category: '선택', | |
203 | + name: selectedUser.name, // or other fields if needed | |
204 | + }); | |
205 | + this.showPopup2 = false; // 팝업 닫기 | |
206 | + }, | |
207 | + // 승인자 삭제 | |
208 | + removeApproval(index) { | |
209 | + this.approvals1.splice(index, 1); | |
210 | + }, | |
211 | + | |
212 | + calculateDayCount() { | |
213 | + const start = new Date(`${this.startDate}T${this.startTime}:00`); | |
214 | + const end = new Date(`${this.endDate}T${this.endTime}:00`); | |
215 | + | |
216 | + let totalHours = (end - start) / (1000 * 60 * 60); // 밀리초를 시간 단위로 변환 | |
217 | + | |
218 | + if (this.startDate !== this.endDate) { | |
219 | + // 시작일과 종료일이 다른경우 | |
220 | + const startDateObj = new Date(this.startDate); | |
221 | + const endDateObj = new Date(this.endDate); | |
222 | + const daysDifference = (endDateObj - startDateObj) / (1000 * 60 * 60 * 24); // 두 날짜 사이의 차이를 일수로 계산 | |
223 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
224 | + this.dayCount = daysDifference + 0.5; // 시간 조건이 기준에서 벗어날 경우 | |
225 | + } else { | |
226 | + this.dayCount = Math.ceil(daysDifference + 1); // 시간 조건이 기준에 맞을 경우 | |
227 | + } | |
228 | + } else { | |
229 | + // 시작일과 종료일이 같은 경우 | |
230 | + if (this.startTime !== "09:00" || this.endTime !== "18:00") { | |
231 | + this.dayCount = 0.5; // 시작 시간 또는 종료 시간이 기준과 다를 경우 0.5 | |
232 | + } else { | |
233 | + this.dayCount = 1; // 기준 시간(09:00~18:00)이 맞으면 1일로 간주 | |
234 | + } | |
235 | + } | |
236 | + | |
237 | + | |
238 | + }, | |
239 | + | |
240 | + }, | |
241 | + mounted() { | |
242 | + }, | |
243 | + | |
244 | +}; | |
245 | +</script> |
+++ client/views/pages/Manager/task/projectStatue.vue
... | ... | @@ -0,0 +1,245 @@ |
1 | +<template> | |
2 | +<div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">프로젝트 현황</h2> | |
6 | +<!-- //폼그룹 --> | |
7 | + <div class="boxs"> | |
8 | + <div class="color-boxs"> | |
9 | + <div class="box "> | |
10 | + <h3>전체</h3> | |
11 | + 3 | |
12 | + </div> | |
13 | + <div class="box blue"> | |
14 | + <h3>진행중</h3> | |
15 | + 3 | |
16 | + </div> | |
17 | + <div class="box red"> | |
18 | + <h3>미진행</h3> | |
19 | + 3 | |
20 | + </div> | |
21 | + <div class="box green"> | |
22 | + <h3>완료</h3> | |
23 | + 3 | |
24 | + </div> | |
25 | + </div> | |
26 | + </div> | |
27 | + <div class="sch-form-wrap title-wrap"> | |
28 | + <h3><img :src="h3icon" alt="">승인 대기</h3> | |
29 | + <div class="input-group"> | |
30 | + <select name="" id="" class="form-select"> | |
31 | + <option :value="currentYear">{{ currentYear }}년</option> | |
32 | + <option value="all">전체</option> | |
33 | + <option v-for="year in remainingYears" :key="year" :value="year" v-if="year !== currentYear"> | |
34 | + {{ year }}년 | |
35 | + </option> | |
36 | + </select> | |
37 | + <select name="" id="" class="form-select"> | |
38 | + <option :value="currentMonth">{{ currentMonth }}월</option> | |
39 | + <option value="all">전체</option> | |
40 | + <option v-for="month in remainingMonths" :key="month" :value="month" v-if="month !== currentMonth"> | |
41 | + {{ month }}월 | |
42 | + </option> | |
43 | + </select> | |
44 | + <select name="" id="" class="form-select"> | |
45 | + <option value="">부서</option> | |
46 | + </select> | |
47 | + <select name="" id="" class="form-select"> | |
48 | + <option value="">전체</option> | |
49 | + <option value="">프로젝트명</option> | |
50 | + <option value="">PM</option> | |
51 | + </select> | |
52 | + <div class="sch-input"> | |
53 | + <input type="text" class="form-control"> | |
54 | + <button class="ico-sch"><SearchOutlined /></button> | |
55 | + </div> | |
56 | + </div> | |
57 | + </div> | |
58 | + <!-- Table --> | |
59 | + <div class="tbl-wrap"> | |
60 | + <table id="myTable" class="tbl data"> | |
61 | + <!-- 동적으로 <th> 생성 --> | |
62 | + <thead> | |
63 | + <tr> | |
64 | + <th>구분 </th> | |
65 | + <th>부서</th> | |
66 | + <th>프로젝트명</th> | |
67 | + <th>PM</th> | |
68 | + <th>사업비</th> | |
69 | + <th>기간</th> | |
70 | + <th>상태</th> | |
71 | + </tr> | |
72 | + </thead> | |
73 | + <!-- 동적으로 <td> 생성 --> | |
74 | + <tbody> | |
75 | + <tr | |
76 | + v-for="(item, index) in listData" | |
77 | + :key="index" | |
78 | + @click="goToDetailPage(item)" | |
79 | + :class="{ 'expired': isPastStatus(item.status) }" | |
80 | + > | |
81 | + <td>{{ item.type }}</td> | |
82 | + <td>{{ item.department }}</td> | |
83 | + <td>{{ item.projectName }}</td> | |
84 | + <td>{{ item.pm }}</td> | |
85 | + <td>{{ item.budget }}</td> | |
86 | + <td>{{ item.period }}</td> | |
87 | + <td :class="getStatusClass(item.status)"> | |
88 | + {{ item.status }} | |
89 | + </td> | |
90 | + </tr> | |
91 | + </tbody> | |
92 | + </table> | |
93 | + | |
94 | + </div> | |
95 | + <div class="pagination"> | |
96 | + <ul> | |
97 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
98 | + <li | |
99 | + class="arrow" | |
100 | + :class="{ disabled: currentPage === 1 }" | |
101 | + @click="changePage(currentPage - 1)" | |
102 | + > | |
103 | + < | |
104 | + </li> | |
105 | + | |
106 | + <!-- 페이지 번호 --> | |
107 | + <li | |
108 | + v-for="page in totalPages" | |
109 | + :key="page" | |
110 | + :class="{ active: currentPage === page }" | |
111 | + @click="changePage(page)" | |
112 | + > | |
113 | + {{ page }} | |
114 | + </li> | |
115 | + | |
116 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
117 | + <li | |
118 | + class="arrow" | |
119 | + :class="{ disabled: currentPage === totalPages }" | |
120 | + @click="changePage(currentPage + 1)" | |
121 | + > | |
122 | + > | |
123 | + </li> | |
124 | + </ul> | |
125 | + </div> | |
126 | + | |
127 | + </div> | |
128 | + </div> | |
129 | +</div> | |
130 | +</template> | |
131 | + | |
132 | +<script> | |
133 | +import { ref } from 'vue'; | |
134 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
135 | +const currentYear = new Date().getFullYear(); | |
136 | +const currentMonth = new Date().getMonth() + 1; | |
137 | +export default { | |
138 | + data() { | |
139 | + return { | |
140 | + currentMonth, | |
141 | + selectedMonth: currentMonth, | |
142 | + remainingMonths: Array.from({ length: 12 }, (_, i) => i + 1), | |
143 | + currentYear, | |
144 | + selectedYear: currentYear, | |
145 | + remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), | |
146 | + showOptions: false, | |
147 | + currentPage: 1, | |
148 | + totalPages: 3, | |
149 | + photoicon: "/client/resources/img/photo_icon.png", | |
150 | + // 데이터 초기화 | |
151 | + years: [2023, 2024, 2025], // 연도 목록 | |
152 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
153 | + selectedYear: '', | |
154 | + selectedMonth: '', | |
155 | + listData: [ | |
156 | + { | |
157 | + type: '내부', | |
158 | + department: '기획팀', | |
159 | + projectName: '신규 플랫폼 개발', | |
160 | + pm: '김PM', | |
161 | + budget: '₩50,000,000', | |
162 | + period: '2025-05-01 ~ 2025-08-31', | |
163 | + status: '진행중' | |
164 | + }, | |
165 | + { | |
166 | + type: '내부', | |
167 | + department: '기획팀', | |
168 | + projectName: '신규 플랫폼 개발', | |
169 | + pm: '김PM', | |
170 | + budget: '₩50,000,000', | |
171 | + period: '2025-05-01 ~ 2025-08-31', | |
172 | + status: '미진행' | |
173 | + }, | |
174 | + { | |
175 | + type: '외부', | |
176 | + department: '운영팀', | |
177 | + projectName: '유지보수 프로젝트', | |
178 | + pm: '박PM', | |
179 | + budget: '₩20,000,000', | |
180 | + period: '2025-03-01 ~ 2025-04-30', | |
181 | + status: '완료' | |
182 | + }], | |
183 | + filteredData: [], | |
184 | + }; | |
185 | + }, | |
186 | + components:{ | |
187 | + SearchOutlined | |
188 | + }, | |
189 | + computed: { | |
190 | + }, | |
191 | + methods: { | |
192 | + | |
193 | + goToAttendancePage(item) { | |
194 | + this.$router.push({ name: 'AttendanceDetail', query: { id: item.id } }); | |
195 | + }, | |
196 | + changePage(page) { | |
197 | + if (page < 1 || page > this.totalPages) return; | |
198 | + this.currentPage = page; | |
199 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
200 | + }, | |
201 | + async onClickSubmit() { | |
202 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
203 | + const { mutate, onDone, onError } = useMutation(mygql); | |
204 | + | |
205 | + try { | |
206 | + const result = await mutate(); | |
207 | + console.log(result); | |
208 | + } catch (error) { | |
209 | + console.error('Mutation error:', error); | |
210 | + } | |
211 | + }, | |
212 | + goToDetailPage(item) { | |
213 | + // item.id 또는 다른 식별자를 사용하여 URL을 구성할 수 있습니다. | |
214 | + this.$router.push({ path: `/projectDetail.page`, query: { id: item.id } }); | |
215 | + }, | |
216 | + | |
217 | + // 상태에 따른 클래스 반환 메소드 | |
218 | + getStatusClass(status) { | |
219 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
220 | + }, | |
221 | + getStatusClass(status) { | |
222 | + if (status === '완료') return 'status-green'; | |
223 | + if (status === '진행중') return 'status-approved'; | |
224 | + if (status === '미진행') return 'status-pending'; | |
225 | + return ''; | |
226 | + }, | |
227 | + isPastStatus(status) { | |
228 | + return status === '완료' ; // 조건은 필요 시 조정 | |
229 | + }, | |
230 | + }, | |
231 | + created() { | |
232 | + }, | |
233 | + mounted() { | |
234 | + | |
235 | + | |
236 | + }, | |
237 | +}; | |
238 | +</script> | |
239 | + | |
240 | +<style scoped> | |
241 | +tr{cursor: pointer;} | |
242 | +.content .card .color-boxs .box{ | |
243 | + width: calc(100% / 4); | |
244 | +} | |
245 | +</style> |
+++ client/views/pages/Manager/task/projectTuib.vue
... | ... | @@ -0,0 +1,198 @@ |
1 | +<template> | |
2 | +<div class="col-lg-12"> | |
3 | + <div class="card"> | |
4 | + <div class="card-body"> | |
5 | + <h2 class="card-title">투입 현황</h2> | |
6 | + <div class="sch-form-wrap"> | |
7 | + <div class="input-group"> | |
8 | + <select name="" id="" class="form-select"> | |
9 | + <option :value="currentYear">{{ currentYear }}년</option> | |
10 | + <option value="all">전체</option> | |
11 | + <option | |
12 | + v-for="year in remainingYears" | |
13 | + :key="year" | |
14 | + :value="year" | |
15 | + v-if="year !== currentYear" | |
16 | + > | |
17 | + {{ year }}년 | |
18 | + </option> | |
19 | + </select> | |
20 | + <select name="" id="" class="form-select"> | |
21 | + <option value="">구분</option> | |
22 | + </select> | |
23 | + <div class="sch-input"> | |
24 | + <input type="text" class="form-control" placeholder="이름"> | |
25 | + <button class="ico-sch"><SearchOutlined /></button> | |
26 | + </div> | |
27 | + </div> | |
28 | + </div> | |
29 | + | |
30 | + <!-- Table --> | |
31 | + <div class="tbl-wrap"> | |
32 | + <table id="myTable" class="tbl data buseo"> | |
33 | + <!-- 동적으로 <th> 생성 --> | |
34 | + <thead> | |
35 | + <tr> | |
36 | + <th rowspan="2">No </th> | |
37 | + <th rowspan="2">부서</th> | |
38 | + <th rowspan="2">직급</th> | |
39 | + <th rowspan="2">이름</th> | |
40 | + <th colspan="3">프로젝트</th> | |
41 | + </tr> | |
42 | + <tr> | |
43 | + <th><p >진행전</p></th> | |
44 | + <th><p class="blue">진행중</p></th> | |
45 | + <th><p class="green">완료</p></th> | |
46 | + </tr> | |
47 | + </thead> | |
48 | + <!-- 동적으로 <td> 생성 --> | |
49 | + <tbody> | |
50 | + <tr v-for="(item, index) in listData" :key="index" @click="goToAttendancePage(item)"> | |
51 | + <td>{{ index + 1 }}</td> | |
52 | + <td>{{ item.department }}</td> | |
53 | + <td>{{ item.position }}</td> | |
54 | + <td>{{ item.name }}</td> | |
55 | + <td>{{ item.projectBefore }}</td> | |
56 | + <td>{{ item.projectOngoing }}</td> | |
57 | + <td>{{ item.projectDone }}</td> | |
58 | + </tr> | |
59 | + </tbody> | |
60 | + </table> | |
61 | + | |
62 | + </div> | |
63 | + <div class="pagination"> | |
64 | + <ul> | |
65 | + <!-- 왼쪽 화살표 (이전 페이지) --> | |
66 | + <li | |
67 | + class="arrow" | |
68 | + :class="{ disabled: currentPage === 1 }" | |
69 | + @click="changePage(currentPage - 1)" | |
70 | + > | |
71 | + < | |
72 | + </li> | |
73 | + | |
74 | + <!-- 페이지 번호 --> | |
75 | + <li | |
76 | + v-for="page in totalPages" | |
77 | + :key="page" | |
78 | + :class="{ active: currentPage === page }" | |
79 | + @click="changePage(page)" | |
80 | + > | |
81 | + {{ page }} | |
82 | + </li> | |
83 | + | |
84 | + <!-- 오른쪽 화살표 (다음 페이지) --> | |
85 | + <li | |
86 | + class="arrow" | |
87 | + :class="{ disabled: currentPage === totalPages }" | |
88 | + @click="changePage(currentPage + 1)" | |
89 | + > | |
90 | + > | |
91 | + </li> | |
92 | + </ul> | |
93 | + </div> | |
94 | + | |
95 | + </div> | |
96 | + </div> | |
97 | +</div> | |
98 | +</template> | |
99 | + | |
100 | +<script> | |
101 | +import { ref } from 'vue'; | |
102 | +import { SearchOutlined } from '@ant-design/icons-vue'; | |
103 | +const currentYear = new Date().getFullYear(); | |
104 | +export default { | |
105 | + data() { | |
106 | + return { | |
107 | + currentYear, | |
108 | + selectedYear: currentYear, | |
109 | + remainingYears: Array.from({ length: 10 }, (_, i) => currentYear - i), | |
110 | + showOptions: false, | |
111 | + currentPage: 1, | |
112 | + totalPages: 3, | |
113 | + photoicon: "/client/resources/img/photo_icon.png", | |
114 | + // 데이터 초기화 | |
115 | + years: [2023, 2024, 2025], // 연도 목록 | |
116 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
117 | + selectedYear: '', | |
118 | + selectedMonth: '', | |
119 | + listData: [ | |
120 | + { | |
121 | + department: '기획팀', | |
122 | + position: '대리', | |
123 | + name: '홍길동', | |
124 | + projectBefore: 2, | |
125 | + projectOngoing: 1, | |
126 | + projectDone: 3 | |
127 | + }, | |
128 | + { | |
129 | + department: '개발팀', | |
130 | + position: '과장', | |
131 | + name: '김개발', | |
132 | + projectBefore: 0, | |
133 | + projectOngoing: 2, | |
134 | + projectDone: 5 | |
135 | + } | |
136 | + ], | |
137 | + filteredData: [], | |
138 | + }; | |
139 | + }, | |
140 | + components:{ | |
141 | + SearchOutlined | |
142 | + }, | |
143 | + computed: { | |
144 | + }, | |
145 | + methods: { | |
146 | + goToAttendancePage(item) { | |
147 | + this.$router.push({ name: 'projectTuibDetail', query: { id: item.id } }); | |
148 | + }, | |
149 | + changePage(page) { | |
150 | + if (page < 1 || page > this.totalPages) return; | |
151 | + this.currentPage = page; | |
152 | + this.$emit('page-changed', page); // 필요 시 부모에 알림 | |
153 | + }, | |
154 | + async onClickSubmit() { | |
155 | + // `useMutation` 훅을 사용하여 mutation 함수 가져오기 | |
156 | + const { mutate, onDone, onError } = useMutation(mygql); | |
157 | + | |
158 | + try { | |
159 | + const result = await mutate(); | |
160 | + console.log(result); | |
161 | + } catch (error) { | |
162 | + console.error('Mutation error:', error); | |
163 | + } | |
164 | + }, | |
165 | + goToPage(type) { | |
166 | + if (type === '휴가') { | |
167 | + this.$router.push('/HyugaDetail.page'); | |
168 | + } else if (type === '출장') { | |
169 | + this.$router.push('/ChuljangDetail.page'); | |
170 | + } | |
171 | +}, | |
172 | + getStatusClass(status) { | |
173 | + if (status === '승인') return 'status-approved'; | |
174 | + if (status === '대기') return 'status-pending'; | |
175 | + return ''; | |
176 | + }, | |
177 | + isPastPeriod(period) { | |
178 | + // 예: '2025-05-01 ~ 2025-05-03' → 종료일 추출 | |
179 | + const endDateStr = period.split('~')[1]?.trim(); | |
180 | + if (!endDateStr) return false; | |
181 | + | |
182 | + const endDate = new Date(endDateStr); | |
183 | + const today = new Date(); | |
184 | + | |
185 | + // 현재 날짜보다 과거면 true | |
186 | + return endDate < today; | |
187 | + } | |
188 | + }, | |
189 | + created() { | |
190 | + }, | |
191 | + mounted() { | |
192 | + | |
193 | + | |
194 | + }, | |
195 | +}; | |
196 | +</script> | |
197 | + | |
198 | +<style scoped></style> |
+++ client/views/pages/Manager/task/projectTuibDetail.vue
... | ... | @@ -0,0 +1,183 @@ |
1 | +<template> | |
2 | + <div class="card "> | |
3 | + <div class="card-body"> | |
4 | + <h2 class="card-title">투입 현황</h2> | |
5 | + <div class="name-box flex sb simple"> | |
6 | + <div class="img-area"> | |
7 | + <div class="img"><img :src="photoicon" alt=""> | |
8 | + </div> | |
9 | + </div> | |
10 | + <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" | |
11 | + @submit.prevent="handleRegister" novalidate> | |
12 | + <div class="col-12"> | |
13 | + <label for="yourName" class="form-label">아이디</label> | |
14 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly | |
15 | + placeholder="admin"> | |
16 | + </div> | |
17 | + <div class="col-12 "> | |
18 | + <div class="col-12 border-x"> | |
19 | + <label for="youremail" class="form-label ">이름<p class="require"><img :src="require" alt=""></p></label> | |
20 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly> | |
21 | + </div> | |
22 | + | |
23 | + <div class="col-12 border-x"> | |
24 | + <label for="yourPassword" class="form-label">부서</label> | |
25 | + <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" | |
26 | + required readonly placeholder="주식회사 테이큰 소프트"> | |
27 | + </div> | |
28 | + </div> | |
29 | + <div class="col-12 border-x"> | |
30 | + <div class="col-12 border-x"> | |
31 | + <label for="youremail" class="form-label">직급</label> | |
32 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly | |
33 | + placeholder="과장"> | |
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" id="yourPassword" | |
39 | + required readonly placeholder="팀장"> | |
40 | + </div> | |
41 | + </div> | |
42 | + | |
43 | + | |
44 | + </form> | |
45 | + </div> | |
46 | + <GoogleCalendar /> | |
47 | + <div class="tbl-wrap" style="margin-top: 3rem;"> | |
48 | + <table id="myTable" class="tbl data"> | |
49 | + <!-- 동적으로 <th> 생성 --> | |
50 | + <thead> | |
51 | + <tr> | |
52 | + <th>구분 </th> | |
53 | + <th>프로젝트명</th> | |
54 | + <th>PM</th> | |
55 | + <th>사업비</th> | |
56 | + <th>기간</th> | |
57 | + <th>상태</th> | |
58 | + </tr> | |
59 | + </thead> | |
60 | + <!-- 동적으로 <td> 생성 --> | |
61 | + <tbody> | |
62 | + <tr v-for="(item, index) in listData" :key="index" :class="{ 'expired': isPastStatus(item.status) }" > | |
63 | + <td>{{ item.type }}</td> | |
64 | + <td>{{ item.projectName }}</td> | |
65 | + <td>{{ item.pm }}</td> | |
66 | + <td>{{ formatBudget(item.budget) }}</td> | |
67 | + <td>{{ item.period }}</td> | |
68 | + <td :class="getStatusClass(item.status)"> | |
69 | + {{ item.status }} | |
70 | + </td> | |
71 | + </tr> | |
72 | + </tbody> | |
73 | + </table> | |
74 | + | |
75 | + </div> | |
76 | + <div class="buttons"> | |
77 | + <button type="submit" class="btn tertiary">목록</button> | |
78 | + </div> | |
79 | + </div> | |
80 | +</div> | |
81 | + | |
82 | +</template> | |
83 | + | |
84 | +<script> | |
85 | +import GoogleCalendar from "../../../component/GoogleCalendar.vue" | |
86 | + | |
87 | +export default { | |
88 | + data() { | |
89 | + return { | |
90 | + photoicon: "/client/resources/img/photo_icon.png", | |
91 | + img1: "/client/resources/img/img.png", | |
92 | + icon1: "/client/resources/img/icon.png", | |
93 | + dateicon: "/client/resources/img/date.png", | |
94 | + startbtn: "/client/resources/img/start.png", | |
95 | + stopbtn: "/client/resources/img/stop.png", | |
96 | + moreicon: "/client/resources/img/more.png", | |
97 | + today: new Date().toLocaleDateString('ko-KR', { | |
98 | + year: 'numeric', | |
99 | + month: '2-digit', | |
100 | + day: '2-digit', | |
101 | + weekday: 'short', | |
102 | + }), | |
103 | + time: this.getCurrentTime(), | |
104 | + listData: [ | |
105 | + { | |
106 | + type: '내부', | |
107 | + projectName: 'AI 개발 프로젝트', | |
108 | + pm: '홍길동', | |
109 | + budget: 50000000, | |
110 | + period: '2024-01-01 ~ 2024-12-31', | |
111 | + status: '진행중' | |
112 | + }, | |
113 | + { | |
114 | + type: '외부', | |
115 | + projectName: '웹 리뉴얼', | |
116 | + pm: '김영희', | |
117 | + budget: 20000000, | |
118 | + period: '2023-01-01 ~ 2023-12-31', | |
119 | + status: '미진행' | |
120 | + }, | |
121 | + { | |
122 | + type: '외부', | |
123 | + projectName: '웹 리뉴얼', | |
124 | + pm: '김영희', | |
125 | + budget: 20000000, | |
126 | + period: '2023-01-01 ~ 2023-12-31', | |
127 | + status: '완료' | |
128 | + } | |
129 | + ] | |
130 | + } | |
131 | + }, | |
132 | + methods: { | |
133 | + formatBudget(amount) { | |
134 | + return new Intl.NumberFormat().format(amount) + ' 원'; | |
135 | + }, | |
136 | + getStatusClass(status) { | |
137 | + return status === 'active' ? 'status-active' : 'status-inactive'; | |
138 | + }, | |
139 | + getStatusClass(status) { | |
140 | + if (status === '완료') return 'status-green'; | |
141 | + if (status === '진행중') return 'status-approved'; | |
142 | + if (status === '미진행') return 'status-pending'; | |
143 | + return ''; | |
144 | + }, | |
145 | + isPastStatus(status) { | |
146 | + return status === '완료' ; // 조건은 필요 시 조정 | |
147 | + }, | |
148 | + getCurrentTime() { | |
149 | + const now = new Date(); | |
150 | + const hours = String(now.getHours()).padStart(2, '0'); | |
151 | + const minutes = String(now.getMinutes()).padStart(2, '0'); | |
152 | + const seconds = String(now.getSeconds()).padStart(2, '0'); | |
153 | + return `${hours}:${minutes}:${seconds}`; | |
154 | + }, | |
155 | + getCategoryClass(category) { | |
156 | + switch (category) { | |
157 | + case '용역': return 'category-service'; | |
158 | + case '내부': return 'category-internal'; | |
159 | + case '국가과제': return 'category-government'; | |
160 | + default: return ''; | |
161 | + } | |
162 | + }, | |
163 | + }, | |
164 | + watch: { | |
165 | + | |
166 | + }, | |
167 | + computed: { | |
168 | + | |
169 | + }, | |
170 | + components: { | |
171 | + GoogleCalendar, | |
172 | + }, | |
173 | + mounted() { | |
174 | + console.log('main mounted'); | |
175 | + setInterval(() => { | |
176 | + this.time = this.getCurrentTime(); | |
177 | + }, 1000); | |
178 | + } | |
179 | +} | |
180 | +</script> | |
181 | +<style scoped> | |
182 | +tr{cursor: pointer;} | |
183 | +</style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/Manager/task/task.vue
+++ client/views/pages/Manager/task/task.vue
... | ... | @@ -1,165 +1,90 @@ |
1 | 1 |
<template> |
2 |
- <div class="pagetitle"> |
|
3 |
- <h2>공지사항</h2> |
|
4 |
- </div> |
|
5 |
- <!-- End Page Title --> |
|
6 |
- <section class="section"> |
|
7 |
- <div class="row"> |
|
8 |
- |
|
9 |
- |
|
10 |
- <div class="col-lg-12"> |
|
11 |
- <div class="card"> |
|
12 |
- <div class="card-body"> |
|
13 |
- <h5 class="card-title"></h5> |
|
14 |
- <div class="d-flex pb-3 justify-content-between"> |
|
15 |
- <div class="datatable-search d-flex gap-1 "> |
|
16 |
- <div class=""> |
|
17 |
- <select class="form-select " v-model="selectedDept"> |
|
18 |
- <option value="" >이름</option> |
|
19 |
- <option v-for="dept in depts" :key="dept" :value="dept">{{ dept }}</option> |
|
20 |
- </select> |
|
21 |
- </div> |
|
22 |
- <div class="search-bar d-flex gap-2"> |
|
23 |
- <form class="search-form d-flex align-items-center" method="POST" action="#" |
|
24 |
- @submit.prevent="updateMember"> |
|
25 |
- <input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword"> |
|
26 |
- <button type="submit" title="Search"><i class="bi bi-search"></i></button> |
|
27 |
- </form> |
|
28 |
- </div> |
|
29 |
- <button type="button" class="btn btn-outline-secondary" |
|
30 |
- @click="filterData">조회</button> |
|
31 |
- |
|
32 |
- </div> |
|
33 |
- <div class="d-flex justify-content-end "> |
|
34 |
- <button type="button" class="btn btn-outline-primary" @click="registerLeave"> |
|
35 |
- 등록 |
|
36 |
- </button> |
|
37 |
- <!-- <button type="button" class="btn btn-outline-success" @click="saveChanges"> |
|
38 |
- 저장 |
|
39 |
- </button> --> |
|
40 |
- <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
41 |
- 삭제 |
|
42 |
- </button> |
|
43 |
- </div> |
|
44 |
- |
|
45 |
- </div> |
|
46 |
- <!-- Table --> |
|
47 |
- <table id="myTable" class="table datatable table-hover"> |
|
48 |
- <colgroup> |
|
49 |
- <col width="10%"> |
|
50 |
- <col width="75%"> |
|
51 |
- <col width="5%"> |
|
52 |
- <col width="5%"> |
|
53 |
- <col width="5%"> |
|
54 |
- </colgroup> |
|
55 |
- <!-- 동적으로 <th> 생성 --> |
|
56 |
- <thead> |
|
57 |
- <tr> |
|
58 |
- <th>No </th> |
|
59 |
- <th>제목</th> |
|
60 |
- <th>작성자</th> |
|
61 |
- <th>작성일</th> |
|
62 |
- <th>조회수</th> |
|
63 |
- </tr> |
|
64 |
- </thead> |
|
65 |
- <!-- 동적으로 <td> 생성 --> |
|
66 |
- <tbody> |
|
67 |
- <tr v-for="(item, index) in filteredData" :key="item.startDate + index"> |
|
68 |
- <td> |
|
69 |
- <div class="form-check"> |
|
70 |
- <label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> |
|
71 |
- <input v-model="item.acceptTerms" class="form-check-input" type="checkbox" /> |
|
72 |
- </div> |
|
73 |
- </td> |
|
74 |
- <td><input type="text" v-model="item.theme" /></td> |
|
75 |
- <td><input type="text" v-model="item.name" /></td> |
|
76 |
- <td><input type="text" v-model="item.date" /></td> |
|
77 |
- <td><input type="text" v-model="item.views" /></td> |
|
78 |
-</tr> |
|
79 |
- </tbody> |
|
80 |
- </table> |
|
81 |
- |
|
82 |
- <!-- End Table --> |
|
2 |
+ <div class="sidemenu"> |
|
3 |
+ <div class="myinfo simple"> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div><img :src="photoicon" alt=""> |
|
7 |
+ <p class="name">OOO과장</p> |
|
8 |
+ </div> |
|
9 |
+ <div class="info"> |
|
10 |
+ <p>솔루션 개발팀</p> |
|
11 |
+ <i class="fa-bars"></i> |
|
12 |
+ <p>팀장</p> |
|
83 | 13 |
</div> |
84 | 14 |
</div> |
85 | 15 |
</div> |
16 |
+ |
|
17 |
+ |
|
18 |
+ <details class="menu-box"> |
|
19 |
+ <summary><p>프로젝트</p><div class="icon"><img :src="topmenuicon" alt=""></div></summary> |
|
20 |
+ <ul> |
|
21 |
+ <li> <router-link to="/projectStatue.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
22 |
+ <p>프로젝트 현황</p> |
|
23 |
+ <div class="icon" v-if="isExactActive"> |
|
24 |
+ <img :src="menuicon" alt=""> |
|
25 |
+ </div> |
|
26 |
+ </router-link></li> |
|
27 |
+ <li> |
|
28 |
+ <router-link to="/projectInsert.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
29 |
+ <p>프로젝트 등록</p> |
|
30 |
+ <div class="icon" v-if="isExactActive"> |
|
31 |
+ <img :src="menuicon" alt=""> |
|
32 |
+ </div> |
|
33 |
+ </router-link> |
|
34 |
+ </li> |
|
35 |
+ <li> |
|
36 |
+ <router-link to="/projectTuib.page" exact-active-class="active-link" v-slot="{ isExactActive }"> |
|
37 |
+ <p>투입현황</p> |
|
38 |
+ <div class="icon" v-if="isExactActive"> |
|
39 |
+ <img :src="menuicon" alt=""> |
|
40 |
+ </div> |
|
41 |
+ </router-link> |
|
42 |
+ </li> |
|
43 |
+ </ul> |
|
44 |
+ </details> |
|
86 | 45 |
</div> |
87 |
- </section> |
|
46 |
+ </div> |
|
47 |
+ <!-- End Page Title --> |
|
48 |
+ <div class="content"> |
|
49 |
+ <router-view></router-view> |
|
50 |
+ |
|
51 |
+ </div> |
|
88 | 52 |
</template> |
89 | 53 |
|
90 | 54 |
<script> |
55 |
+import { ref } from 'vue'; |
|
56 |
+ |
|
91 | 57 |
export default { |
92 | 58 |
data() { |
93 | 59 |
return { |
60 |
+ photoicon: "/client/resources/img/photo_icon.png", |
|
61 |
+ menuicon: "/client/resources/img/menuicon.png", |
|
62 |
+ topmenuicon: "/client/resources/img/topmenuicon.png", |
|
94 | 63 |
// 데이터 초기화 |
95 |
- depts: [2023, 2024, 2025], // 연도 목록 |
|
96 |
- levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
97 |
- selectedDept: '', |
|
98 |
- selectedlevel: '', |
|
64 |
+ years: [2023, 2024, 2025], // 연도 목록 |
|
65 |
+ months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
66 |
+ selectedYear: '', |
|
67 |
+ selectedMonth: '', |
|
68 |
+ DeptData: [ |
|
69 |
+ { member: '', deptNM: '', acceptTerms: false }, |
|
70 |
+ // 더 많은 데이터 추가... |
|
71 |
+ ], |
|
99 | 72 |
filteredData: [], |
100 | 73 |
}; |
101 | 74 |
}, |
102 | 75 |
computed: { |
103 |
- |
|
104 | 76 |
}, |
105 | 77 |
methods: { |
106 |
- registerLeave() { |
|
107 |
- // "NoticeInsert" 페이지로 이동 |
|
108 |
- this.$router.push({ path: '/NoticeInsert' }); |
|
109 |
- }, |
|
110 |
- |
|
111 |
- deletePending() { |
|
112 |
- // 선택된 항목만 필터링하여 삭제 |
|
113 |
- const selectedItems = this.NoticegData.filter(item => item.acceptTerms); |
|
114 | 78 |
|
115 |
- // 승인된 항목이 없으면 삭제 진행 |
|
116 |
- if (selectedItems.length > 0) { |
|
117 |
- this.NoticegData = this.NoticegData.filter(item => !item.acceptTerms); |
|
118 |
- alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); |
|
119 |
- } else { |
|
120 |
- alert("선택된 항목이 없습니다."); |
|
121 |
- } |
|
122 |
- }, |
|
123 |
- filterData() { |
|
124 |
- this.filteredData = this.NoticegData.filter(item => { |
|
125 |
- const matchesDept = this.selectedDept ? item.where.includes(this.selectedDept) : true; |
|
126 |
- const matchesQuery = this.searchQuery ? item.theme.includes(this.searchQuery) : true; |
|
127 |
- return matchesDept && matchesQuery; |
|
128 |
- }); |
|
129 |
- }, |
|
130 |
- |
|
131 | 79 |
// 페이지 변경 |
132 | 80 |
changePage(page) { |
133 | 81 |
this.currentPage = page; |
134 | 82 |
}, |
135 |
- loadFormData() { |
|
136 |
- const savedData = localStorage.getItem('formData'); |
|
137 |
- console.log(savedData) |
|
138 |
- if (savedData) { |
|
139 |
- const formData = JSON.parse(savedData); |
|
140 |
- this.NoticeData = formData.map(item => ({ |
|
141 |
- ...item, |
|
142 |
- acceptTerms: false, // 추가적인 필드 (체크박스) |
|
143 |
- })); |
|
144 |
- this.filteredData = [...this.NoticeData]; // 필터링된 데이터 초기화 |
|
145 |
- } |
|
146 |
- }, |
|
147 | 83 |
}, |
148 | 84 |
created() { |
149 |
- // 로컬스토리지에서 UserInfoData 불러오기 |
|
150 |
- const storedUserInfo = localStorage.getItem('formData'); |
|
151 |
- console.log(storedUserInfo); |
|
152 |
- if (storedUserInfo) { |
|
153 |
- // 로컬스토리지에서 데이터를 가져와 UserInfoData에 설정 |
|
154 |
- const parsedData = JSON.parse(storedUserInfo); |
|
155 |
- this.UserInfo = Array.isArray(parsedData) ? parsedData : [parsedData]; |
|
156 |
- } |
|
157 | 85 |
}, |
158 | 86 |
mounted() { |
159 |
- |
|
160 |
- // 처음에는 모든 데이터를 표시 |
|
161 |
- this.filteredData = this.NoticegData; |
|
162 |
- |
|
87 |
+ |
|
163 | 88 |
}, |
164 | 89 |
}; |
165 | 90 |
</script> |
--- client/views/pages/User/MyPage.vue
+++ client/views/pages/User/MyPage.vue
... | ... | @@ -1,103 +1,116 @@ |
1 | 1 |
<template> |
2 | 2 |
<div class="sidemenu"> |
3 | 3 |
<div class="myinfo profile"> |
4 |
- <div class="name-box"> |
|
5 |
- <div class="img-area"> |
|
6 |
- <div class="img"> |
|
7 |
- <img :src="previewImg || placeholder" alt="미리보기 이미지" /> |
|
8 |
- <button class="close-btn" @click="removeImage">×</button> |
|
9 |
- </div> |
|
10 |
- <div class="info"> |
|
11 |
- <div class="file"> |
|
12 |
- <label for="fileUpload" class="file-label"><div><img :src="file" alt=""></div><p>업로드</p></label> |
|
13 |
- <input id="fileUpload" type="file" @change="handleFileUpload" accept="image/*" /> |
|
4 |
+ <div class="name-box"> |
|
5 |
+ <div class="img-area"> |
|
6 |
+ <div class="img"> |
|
7 |
+ <img :src="previewImg || placeholder" alt="미리보기 이미지" /> |
|
8 |
+ <button class="close-btn" @click="removeImage">×</button> |
|
9 |
+ </div> |
|
10 |
+ <div class="info"> |
|
11 |
+ <div class="file"> |
|
12 |
+ <label for="fileUpload" class="file-label"> |
|
13 |
+ <img :src="file" alt=""> |
|
14 |
+ <p>업로드</p> |
|
15 |
+ </label> |
|
16 |
+ <input id="fileUpload" type="file" @change="handleFileUpload" accept="image/*" /> |
|
17 |
+ </div> |
|
18 |
+ </div> |
|
19 |
+ </div> |
|
14 | 20 |
</div> |
15 | 21 |
</div> |
16 | 22 |
</div> |
17 |
- </div> |
|
18 |
- </div> |
|
19 |
- </div> |
|
20 | 23 |
<div class="content"> |
21 | 24 |
|
22 |
- <div class="d-flex justify-content-center py-4"> |
|
23 |
- <a href="index.html" class="logo d-flex align-items-center w-auto"> |
|
24 |
- <!-- <span class="d-none d-lg-block"> <img :src="logo" alt=""></span> --> |
|
25 |
- </a> |
|
26 |
- </div><!-- End Logo --> |
|
25 |
+ <div class="d-flex justify-content-center py-4"> |
|
26 |
+ <a href="index.html" class="logo d-flex align-items-center w-auto"> |
|
27 |
+ <!-- <span class="d-none d-lg-block"> <img :src="logo" alt=""></span> --> |
|
28 |
+ </a> |
|
29 |
+ </div><!-- End Logo --> |
|
27 | 30 |
|
28 |
- <div class="card mb-3"> |
|
29 |
- <p class="require"><img :src="require" alt=""> 필수입력</p> |
|
30 |
- <div class="card-body"> |
|
31 |
+ <div class="card mb-3"> |
|
32 |
+ <p class="require"><img :src="require" alt=""> 필수입력</p> |
|
33 |
+ <div class="card-body"> |
|
31 | 34 |
|
32 |
- <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> |
|
33 |
- <div class="col-12"> |
|
34 |
- <label for="yourName" class="form-label">아이디</label> |
|
35 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly placeholder="admin"> |
|
36 |
- </div> |
|
37 |
- <div class="col-12 "> |
|
38 |
- <div class="col-12 border-x"> |
|
39 |
- <label for="youremail" class="form-label ">이름<p class="require"><img :src="require" alt=""></p></label> |
|
40 |
- <input v-model="email" type="text" name="username" class="form-control" id="youremail" required > |
|
41 |
- </div> |
|
42 |
- |
|
43 |
- <div class="col-12 border-x"> |
|
44 |
- <label for="yourPassword" class="form-label">부서</label> |
|
45 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required readonly placeholder="주식회사 테이큰 소프트"> |
|
46 |
- </div> |
|
47 |
- </div> |
|
48 |
- <div class="col-12"> |
|
49 |
- <div class="col-12 border-x"> |
|
50 |
- <label for="youremail" class="form-label">직급</label> |
|
51 |
- <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly placeholder="과장"> |
|
52 |
- </div> |
|
53 |
- |
|
54 |
- <div class="col-12 border-x"> |
|
55 |
- <label for="yourPassword" class="form-label">직책</label> |
|
56 |
- <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required readonly placeholder="팀장"> |
|
57 |
- </div> |
|
58 |
- </div> |
|
59 |
- <div class="col-12"> |
|
60 |
- <label for="yourName" class="form-label">연락처</label> |
|
61 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
35 |
+ <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" |
|
36 |
+ @submit.prevent="handleRegister" novalidate> |
|
37 |
+ <div class="col-12"> |
|
38 |
+ <label for="yourName" class="form-label">아이디</label> |
|
39 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly |
|
40 |
+ placeholder="admin"> |
|
41 |
+ </div> |
|
42 |
+ <div class="col-12 "> |
|
43 |
+ <div class="col-12 border-x"> |
|
44 |
+ <label for="youremail" class="form-label "> |
|
45 |
+ <p>이름 |
|
46 |
+ <p class="require"><img :src="require" alt=""></p> |
|
47 |
+ </p> |
|
48 |
+ </label> |
|
49 |
+ <input v-model="email" type="text" name="username" class="form-control" id="youremail" required> |
|
50 |
+ </div> |
|
51 |
+ |
|
52 |
+ <div class="col-12 border-x"> |
|
53 |
+ <label for="yourPassword" class="form-label">부서</label> |
|
54 |
+ <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required |
|
55 |
+ readonly placeholder="주식회사 테이큰 소프트"> |
|
56 |
+ </div> |
|
62 | 57 |
</div> |
63 | 58 |
<div class="col-12"> |
64 |
- <label for="yourName" class="form-label">생년월일</label> |
|
65 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
59 |
+ <div class="col-12 border-x"> |
|
60 |
+ <label for="youremail" class="form-label">직급</label> |
|
61 |
+ <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly |
|
62 |
+ placeholder="과장"> |
|
63 |
+ </div> |
|
64 |
+ |
|
65 |
+ <div class="col-12 border-x"> |
|
66 |
+ <label for="yourPassword" class="form-label">직책</label> |
|
67 |
+ <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required |
|
68 |
+ readonly placeholder="팀장"> |
|
69 |
+ </div> |
|
66 | 70 |
</div> |
67 | 71 |
<div class="col-12"> |
68 |
- <label for="yourName" class="form-label">입사일</label> |
|
69 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly placeholder="2025-01-01"> |
|
72 |
+ <label for="yourName" class="form-label">연락처</label> |
|
73 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
70 | 74 |
</div> |
71 | 75 |
<div class="col-12"> |
72 |
- <label for="yourName" class="form-label">현재 비밀번호</label> |
|
73 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
76 |
+ <label for="yourName" class="form-label">생년월일</label> |
|
77 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
74 | 78 |
</div> |
75 | 79 |
<div class="col-12"> |
76 |
- <label for="yourName" class="form-label">새 비밀번호</label> |
|
77 |
- <div class="box"> |
|
78 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
79 |
- <div class="invalid-feedback">※ 비밀번호는 6~12자의 영문자와 숫자, 특수기호조합으로 작성해주세요.</div> |
|
80 |
- </div> |
|
80 |
+ <label for="yourName" class="form-label">입사일</label> |
|
81 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required readonly |
|
82 |
+ placeholder="2025-01-01"> |
|
83 |
+ </div> |
|
84 |
+ <div class="col-12"> |
|
85 |
+ <label for="yourName" class="form-label">현재 비밀번호</label> |
|
86 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
87 |
+ </div> |
|
88 |
+ <div class="col-12"> |
|
89 |
+ <label for="yourName" class="form-label">새 비밀번호</label> |
|
90 |
+ <div class="box"> |
|
91 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
92 |
+ <div class="invalid-feedback">※ 비밀번호는 6~12자의 영문자와 숫자, 특수기호조합으로 작성해주세요.</div> |
|
93 |
+ </div> |
|
81 | 94 |
</div> |
82 | 95 |
<div class="col-12 border-x"> |
83 |
- <label for="yourName" class="form-label">연락처</label> |
|
84 |
- <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
96 |
+ <label for="yourName" class="form-label">연락처</label> |
|
97 |
+ <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> |
|
85 | 98 |
</div> |
86 |
- |
|
87 |
- </form> |
|
88 |
- <div class="buttons"> |
|
89 |
- <button class="btn primary" type="submit">반려</button> |
|
90 |
- <button class="btn btn-red " type="submit">반려</button> |
|
91 |
- <button class="btn tertiary " type="submit">목록</button> |
|
92 |
- </div> |
|
93 |
- </div> |
|
99 |
+ |
|
100 |
+ </form> |
|
101 |
+ <div class="buttons"> |
|
102 |
+ <button class="btn primary" type="submit">반려</button> |
|
103 |
+ <button class="btn btn-red " type="submit">반려</button> |
|
104 |
+ <button class="btn tertiary " type="submit">목록</button> |
|
94 | 105 |
</div> |
106 |
+ </div> |
|
107 |
+ </div> |
|
95 | 108 |
</div> |
96 | 109 |
|
97 |
- </template> |
|
98 |
- |
|
99 |
- <script> |
|
100 |
- export default { |
|
110 |
+</template> |
|
111 |
+ |
|
112 |
+<script> |
|
113 |
+export default { |
|
101 | 114 |
data() { |
102 | 115 |
return { |
103 | 116 |
previewImg: null, |
... | ... | @@ -126,25 +139,25 @@ |
126 | 139 |
alert('이미지 파일만 선택할 수 있습니다.'); |
127 | 140 |
} |
128 | 141 |
}, |
129 |
- |
|
142 |
+ |
|
130 | 143 |
// 이미지 삭제 함수 |
131 | 144 |
removeImage() { |
132 | 145 |
this.previewImg = null; // 미리보기 이미지 삭제 |
133 | 146 |
this.$refs.fileUpload.value = null; // 파일 input 초기화 |
134 | 147 |
}, |
135 | 148 |
handleRegister() { |
136 |
- this.formSubmitted = true; |
|
149 |
+ this.formSubmitted = true; |
|
137 | 150 |
// 이메일과 비밀번호가 빈 값이 아니어야 한다 |
138 | 151 |
if (!this.email.includes('@')) { |
139 |
- alert('이메일은 @를 포함해야 합니다.'); |
|
140 |
- return; // Stop further processing if email is invalid |
|
141 |
- } |
|
152 |
+ alert('이메일은 @를 포함해야 합니다.'); |
|
153 |
+ return; // Stop further processing if email is invalid |
|
154 |
+ } |
|
142 | 155 |
|
143 |
- console.log('Email:', this.email); |
|
144 |
- console.log('Password:', this.password); |
|
145 |
- console.log('Name:', this.name); |
|
146 |
- console.log('Accept Terms:', this.acceptTerms); |
|
147 |
- if (this.email && this.password && this.name && this.acceptTerms && this.dept && this.level ) { |
|
156 |
+ console.log('Email:', this.email); |
|
157 |
+ console.log('Password:', this.password); |
|
158 |
+ console.log('Name:', this.name); |
|
159 |
+ console.log('Accept Terms:', this.acceptTerms); |
|
160 |
+ if (this.email && this.password && this.name && this.acceptTerms && this.dept && this.level) { |
|
148 | 161 |
// 로컬 스토리지에 회원가입 정보 저장 |
149 | 162 |
const userData = { |
150 | 163 |
name: this.name, |
... | ... | @@ -156,15 +169,15 @@ |
156 | 169 |
|
157 | 170 |
console.log('User Data to be saved:', userData); |
158 | 171 |
try { |
159 |
- localStorage.setItem("UserInfo", JSON.stringify(userData)); |
|
160 |
- alert('회원가입이 완료되었습니다!'); |
|
161 |
- |
|
162 |
- // Redirect to login page |
|
163 |
- this.$router.push("/login"); |
|
164 |
- } catch (error) { |
|
165 |
- console.error("Error saving to localStorage:", error); |
|
166 |
- alert("회원가입 중 오류가 발생했습니다."); |
|
167 |
- } |
|
172 |
+ localStorage.setItem("UserInfo", JSON.stringify(userData)); |
|
173 |
+ alert('회원가입이 완료되었습니다!'); |
|
174 |
+ |
|
175 |
+ // Redirect to login page |
|
176 |
+ this.$router.push("/login"); |
|
177 |
+ } catch (error) { |
|
178 |
+ console.error("Error saving to localStorage:", error); |
|
179 |
+ alert("회원가입 중 오류가 발생했습니다."); |
|
180 |
+ } |
|
168 | 181 |
} else { |
169 | 182 |
alert("모든 필드를 입력해주세요."); |
170 | 183 |
} |
... | ... | @@ -178,9 +191,6 @@ |
178 | 191 |
} |
179 | 192 |
}, |
180 | 193 |
}; |
181 |
- </script> |
|
182 |
- |
|
183 |
- <style scoped> |
|
194 |
+</script> |
|
184 | 195 |
|
185 |
- </style> |
|
186 |
-(파일 끝에 줄바꿈 문자 없음) |
|
196 |
+<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
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?