
--- client/views/index.html
+++ client/views/index.html
... | ... | @@ -12,6 +12,7 @@ |
12 | 12 |
<link rel="stylesheet" href="../../client/resources/css/grid.css"> |
13 | 13 |
<link rel="stylesheet" href="../../client/resources/css/style.css"> |
14 | 14 |
<link rel="stylesheet" href="../../client/resources/css/responsive.css"> |
15 |
+ <link rel="stylesheet" href="../../client/resources/css/palette.scss"> |
|
15 | 16 |
<title>테이큰소프트</title> |
16 | 17 |
</head> |
17 | 18 |
|
--- client/views/index.js
+++ client/views/index.js
... | ... | @@ -5,6 +5,8 @@ |
5 | 5 |
import VueCookies from 'vue-cookies'; |
6 | 6 |
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; |
7 | 7 |
import { provideApolloClient } from '@vue/apollo-composable'; // @vue/apollo-composable 사용 |
8 |
+import Antd from 'ant-design-vue'; |
|
9 |
+ |
|
8 | 10 |
|
9 | 11 |
const httpLink = createHttpLink({ |
10 | 12 |
uri: 'http://backend-example.codebootcamp.co.kr/graphql', // GraphQL 서버 주소 |
... | ... | @@ -50,7 +52,8 @@ |
50 | 52 |
vueApp |
51 | 53 |
.use(AppRouter) // Vue Router 사용 |
52 | 54 |
.use(Store) // Vuex store 사용 |
53 |
- .use(VueCookies) // VueCookies 사용 |
|
55 |
+ .use(VueCookies) |
|
56 |
+ .use(Antd) // VueCookies 사용 |
|
54 | 57 |
.config.globalProperties.$gapi = window.gapi; // gapi를 전역 속성으로 설정 |
55 | 58 |
|
56 | 59 |
// ApolloClient 제공 |
... | ... | @@ -60,6 +63,7 @@ |
60 | 63 |
vueApp.config.globalProperties.$cookies = VueCookies; // 쿠키 설정 |
61 | 64 |
vueApp.$cookies.config("1d"); // 쿠키 만료일 설정 |
62 | 65 |
|
66 |
+ |
|
63 | 67 |
vueApp.mount("#root"); // Vue 앱을 #root에 마운트 |
64 | 68 |
} |
65 | 69 |
|
--- client/views/layout/SideBar.vue
+++ client/views/layout/SideBar.vue
... | ... | @@ -11,17 +11,22 @@ |
11 | 11 |
<ul class="d-flex profile"> |
12 | 12 |
|
13 | 13 |
<li> |
14 |
- <a class="dropdown-item d-flex align-items-center" href="users-profile.html"> |
|
15 |
- <i class="bi bi-person"></i> |
|
16 |
- <span>마이페이지</span> |
|
17 |
- </a> |
|
14 |
+ <router-link to="/MyPage" class="nav-link " active-class="active"><i class="bi bi-grid"></i> |
|
15 |
+ <span>마이페이지</span></router-link> |
|
18 | 16 |
</li> |
19 | 17 |
|
20 | 18 |
<li> |
21 | 19 |
<a class="dropdown-item d-flex align-items-center" href="#"> |
22 | 20 |
<i class="bi bi-box-arrow-right"></i> |
23 |
- <router-link to="/Login" class="nav-link " active-class="active"><i class="bi bi-grid"></i> |
|
24 |
- <span>로그인</span></router-link> |
|
21 |
+ <router-link v-if="!isLoggedIn" to="/Login" class="nav-link" active-class="active"> |
|
22 |
+ <i class="bi bi-grid"></i> |
|
23 |
+ <span>로그인</span> |
|
24 |
+ </router-link> |
|
25 |
+ |
|
26 |
+ <router-link v-if="isLoggedIn" @click="handleLogout" class="nav-link" active-class="active"> |
|
27 |
+ <i class="bi bi-box-arrow-right"></i> |
|
28 |
+ <span>로그아웃</span> |
|
29 |
+ </router-link> |
|
25 | 30 |
</a> |
26 | 31 |
</li> |
27 | 32 |
|
... | ... | @@ -65,6 +70,10 @@ |
65 | 70 |
<li> |
66 | 71 |
<router-link to="/HyugaList" class="nav-link " active-class="active"><i |
67 | 72 |
class="bi bi-circle"></i><span>휴가내역</span></router-link> |
73 |
+ </li> |
|
74 |
+ <li> |
|
75 |
+ <router-link to="/HyugaOk" class="nav-link " active-class="active"><i |
|
76 |
+ class="bi bi-circle"></i><span>휴가승인</span></router-link> |
|
68 | 77 |
</li> |
69 | 78 |
|
70 | 79 |
</ul> |
... | ... | @@ -114,16 +123,30 @@ |
114 | 123 |
name: "ProfileImage", |
115 | 124 |
data() { |
116 | 125 |
return { |
126 |
+ isLoggedIn: false, |
|
117 | 127 |
userName: '', |
118 | 128 |
logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 |
119 | 129 |
}; |
120 | 130 |
}, |
121 | 131 |
methods: { |
132 |
+ handleLogout() { |
|
133 |
+ // Clear login-related data from localStorage |
|
134 |
+ localStorage.removeItem('loggedInUser'); |
|
135 |
+ this.isLoggedIn = false; // Update login status |
|
136 |
+ this.$router.push("/login"); // Redirect to login page after logout |
|
137 |
+ }, |
|
122 | 138 |
toggleSidebar() { |
123 | 139 |
// `toggle-sidebar` 클래스가 있으면 제거, 없으면 추가 |
124 | 140 |
document.body.classList.toggle("toggle-sidebar"); |
125 | 141 |
}, |
126 | 142 |
}, |
143 |
+ created() { |
|
144 |
+ // Check if there is any login data in localStorage |
|
145 |
+ const loggedInUser = localStorage.getItem('loggedInUser'); |
|
146 |
+ if (loggedInUser) { |
|
147 |
+ this.isLoggedIn = true; |
|
148 |
+ } |
|
149 |
+ }, |
|
127 | 150 |
components: { |
128 | 151 |
}, |
129 | 152 |
}; |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -3,17 +3,19 @@ |
3 | 3 |
import Main from '../pages/main/Main.vue'; |
4 | 4 |
import Login from '../pages/User/Login.vue'; |
5 | 5 |
import Join from '../pages/User/Join.vue'; |
6 |
+import MyPage from '../pages/User/MyPage.vue'; |
|
6 | 7 |
|
7 |
-import Main2 from '../pages/main/Main2.vue'; |
|
8 |
+// import Main2 from '../pages/main/Main2.vue'; |
|
8 | 9 |
|
9 |
-import HomeView from "../pages/HomeView.vue"; |
|
10 |
-import AboutView from "../pages/AboutView.vue"; |
|
10 |
+// import HomeView from "../pages/HomeView.vue"; |
|
11 |
+// import AboutView from "../pages/AboutView.vue"; |
|
11 | 12 |
|
12 | 13 |
// 직원 |
13 | 14 |
import ChuljangList from '../pages/Employee/ChuljangList.vue'; |
14 | 15 |
import ChuljangInsert from '../pages/Employee/ChuljangInsert.vue'; |
15 | 16 |
import HyugaList from '../pages/Employee/HyugaList.vue'; |
16 | 17 |
import HyugaInsert from '../pages/Employee/HyugaInsert.vue'; |
18 |
+import HyugaOk from '../pages/Employee/HyugaOk.vue'; |
|
17 | 19 |
import ProjectList from '../pages/Employee/ProjectList.vue'; |
18 | 20 |
import ProjectInsert from '../pages/Employee/ProjectInsert.vue'; |
19 | 21 |
// 관리자 |
... | ... | @@ -28,11 +30,13 @@ |
28 | 30 |
{ path: '/', name: '/', component: Main}, |
29 | 31 |
{ path: '/Login', name: 'Login', component: Login}, |
30 | 32 |
{ path: '/Join', name: 'Join', component: Join}, |
33 |
+ { path: '/MyPage', name: 'MyPage', component: MyPage}, |
|
31 | 34 |
|
32 | 35 |
{ path: '/ChuljangList', name: 'ChuljangList', component: ChuljangList }, |
33 | 36 |
{ path: '/ChuljangInsert', name: 'ChuljangInsert', component: ChuljangInsert }, |
34 | 37 |
{ path: '/HyugaList', name: 'HyugaList', component: HyugaList }, |
35 | 38 |
{ path: '/HyugaInsert', name: 'HyugaInsert', component: HyugaInsert }, |
39 |
+ { path: '/HyugaOk', name: 'HyugaOk', component: HyugaOk }, |
|
36 | 40 |
{ path: '/ProjectInsert', name: 'ProjectInsert', component: ProjectInsert }, |
37 | 41 |
{ path: '/ProjectList', name: 'ProjectList', component: ProjectList }, |
38 | 42 |
{ path: '/DeptList', name: 'DeptList', component: DeptList }, |
--- client/views/pages/Employee/ChuljangInsert.vue
+++ client/views/pages/Employee/ChuljangInsert.vue
... | ... | @@ -8,14 +8,12 @@ |
8 | 8 |
<div class="card-body"> |
9 | 9 |
|
10 | 10 |
<!-- Multi Columns Form --> |
11 |
- <form class="row g-3 pt-3" @submit.prevent="handleSubmit"> |
|
12 |
- |
|
11 |
+ <form class="row g-3 pt-3" @submit.prevent="handleSubmit"> |
|
13 | 12 |
|
14 | 13 |
<div class="col-md-5"> |
15 | 14 |
<label for="startDate" class="form-label">시작일</label> |
16 | 15 |
<div class="d-flex gap-1"> |
17 | 16 |
<input type="date" class="form-control" id="startDate" v-model="startDate" /> |
18 |
- <!-- 시간 선택을 위한 select 사용 --> |
|
19 | 17 |
<select class="form-control" id="startTime" v-model="startTime"> |
20 | 18 |
<option value="09:00">09:00</option> |
21 | 19 |
<option value="10:00">10:00</option> |
... | ... | @@ -35,7 +33,6 @@ |
35 | 33 |
<label for="endDate" class="form-label">종료일</label> |
36 | 34 |
<div class="d-flex gap-1"> |
37 | 35 |
<input type="date" class="form-control" id="endDate" v-model="endDate" /> |
38 |
- <!-- 종료 시간을 위한 select 사용 --> |
|
39 | 36 |
<select class="form-control" id="endTime" v-model="endTime"> |
40 | 37 |
<option value="09:00">09:00</option> |
41 | 38 |
<option value="10:00">10:00</option> |
... | ... | @@ -50,30 +47,34 @@ |
50 | 47 |
</select> |
51 | 48 |
</div> |
52 | 49 |
</div> |
50 |
+ |
|
53 | 51 |
<div class="col-12"> |
54 |
- <label for="prvonsh" class="form-label">출장지</label> |
|
55 |
- <input type="text" class="form-control" id="prvonsh" v-model="reason" /> |
|
52 |
+ <label for="where" class="form-label">출장지</label> |
|
53 |
+ <input type="text" class="form-control" id="where" v-model="where" /> |
|
56 | 54 |
</div> |
55 |
+ |
|
57 | 56 |
<div class="col-12"> |
58 |
- <label for="prvonsh" class="form-label">출장목적</label> |
|
59 |
- <input type="text" class="form-control" id="prvonsh" v-model="reason" /> |
|
57 |
+ <label for="purpose" class="form-label">출장목적</label> |
|
58 |
+ <input type="text" class="form-control" id="purpose" v-model="purpose" /> |
|
60 | 59 |
</div> |
60 |
+ |
|
61 | 61 |
<div class="col-6"> |
62 |
- <label for="prvonsh" class="form-label">동행자</label> |
|
62 |
+ <label for="member" class="form-label">동행자</label> |
|
63 | 63 |
<div class="search-bar d-flex gap-2"> |
64 |
- <form class="search-form d-flex align-items-center" method="POST" action="#" |
|
64 |
+ <form class="search-form d-flex align-items-center" method="POST" action="#" |
|
65 | 65 |
@submit.prevent="updateMember"> |
66 |
- <input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword"> |
|
67 |
- <button type="submit" title="Search"><i class="bi bi-search"></i></button> |
|
66 |
+ <input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword" /> |
|
67 |
+ <button type="submit" title="Search"><PlusOutlined /></button> |
|
68 | 68 |
</form> |
69 |
- <input type="text" class="select-member" :value="selectedMember.join(' ')" readonly> |
|
69 |
+ <input type="text" class="select-member" :value="selectedMember.join(' ')" readonly /> |
|
70 | 70 |
</div> |
71 | 71 |
</div> |
72 | 72 |
|
73 | 73 |
<div class="text-end"> |
74 |
- <button type="submit" class="btn btn-primary">승인요청</button> |
|
75 |
- <button type="reset" class="btn btn-secondary">취소</button> |
|
74 |
+ <button type="submit" class="btn primary">승인요청</button> |
|
75 |
+ <button type="reset" class="btn secondary">취소</button> |
|
76 | 76 |
</div> |
77 |
+ |
|
77 | 78 |
</form><!-- End Multi Columns Form --> |
78 | 79 |
|
79 | 80 |
</div> |
... | ... | @@ -83,38 +84,41 @@ |
83 | 84 |
|
84 | 85 |
<script> |
85 | 86 |
import { useAuthStore } from '../../stores/authStore'; |
87 |
+import { PlusOutlined } from '@ant-design/icons-vue'; |
|
88 |
+ |
|
86 | 89 |
export default { |
87 | 90 |
data() { |
88 | 91 |
const today = new Date().toISOString().split('T')[0]; |
89 | 92 |
return { |
90 |
- searchQuery: '', |
|
91 |
- selectedMember: [], |
|
92 | 93 |
startDate: today, |
93 |
- startTime: "09:00", // 기본 시작 시간 09:00 |
|
94 |
+ startTime: '09:00', |
|
94 | 95 |
endDate: today, |
95 |
- endTime: "18:00", // 기본 종료 시간 18:00 |
|
96 |
- allDay: false, // 종일 여부 |
|
97 |
- dayCount: 1, // 사용 휴가일 계산 |
|
98 |
- reason: "", // 사유 |
|
96 |
+ endTime: '18:00', |
|
97 |
+ where: '', |
|
98 |
+ purpose: '', |
|
99 |
+ selectedMember: [], |
|
100 |
+ searchQuery: '', |
|
99 | 101 |
}; |
100 | 102 |
}, |
103 |
+ components: { |
|
104 |
+ PlusOutlined, |
|
105 |
+ }, |
|
101 | 106 |
computed: { |
102 |
- // Pinia Store의 상태를 가져옵니다. |
|
103 | 107 |
loginUser() { |
104 | 108 |
const authStore = useAuthStore(); |
105 | 109 |
return authStore.getLoginUser; |
106 | 110 |
}, |
107 | 111 |
}, |
112 |
+ |
|
108 | 113 |
methods: { |
109 | 114 |
updateMember() { |
110 |
- // Add the search query to the selectedMembers array if it's not empty |
|
111 |
- if (this.searchQuery.trim()) { |
|
112 |
- this.selectedMember.push(this.searchQuery.trim()); |
|
113 |
- } |
|
114 |
- // Clear the search query after adding it to selectedMembers |
|
115 |
- this.searchQuery = ''; |
|
116 |
- }, |
|
117 |
- // 폼 검증 메서드 |
|
115 |
+ // Add the search query to the selectedMembers array if it's not empty |
|
116 |
+ if (this.searchQuery.trim()) { |
|
117 |
+ this.selectedMember.push(this.searchQuery.trim()); |
|
118 |
+ } |
|
119 |
+ // Clear the search query after adding it to selectedMembers |
|
120 |
+ this.searchQuery = ''; |
|
121 |
+ }, |
|
118 | 122 |
validateForm() { |
119 | 123 |
// 필수 입력 필드 체크 |
120 | 124 |
if ( |
... | ... | @@ -122,8 +126,8 @@ |
122 | 126 |
this.startTime && |
123 | 127 |
this.endDate && |
124 | 128 |
this.endTime && |
125 |
- this.dayCount > 0 && |
|
126 |
- this.reason.trim() !== "" |
|
129 |
+ this.where && |
|
130 |
+ this.purpose.trim() !== "" |
|
127 | 131 |
) { |
128 | 132 |
this.isFormValid = true; |
129 | 133 |
} else { |
... | ... | @@ -160,19 +164,35 @@ |
160 | 164 |
handleSubmit() { |
161 | 165 |
this.validateForm(); // 제출 시 유효성 확인 |
162 | 166 |
if (this.isFormValid) { |
167 |
+ localStorage.setItem('ChuljangFormData', JSON.stringify(this.$data)); |
|
163 | 168 |
alert("승인 요청이 완료되었습니다."); |
164 | 169 |
// 추가 처리 로직 (API 요청 등) |
165 | 170 |
} else { |
166 | 171 |
alert("모든 필드를 올바르게 작성해주세요."); |
167 | 172 |
} |
168 | 173 |
}, |
174 |
+ loadFormData() { |
|
175 |
+ const savedData = localStorage.getItem('ChuljangFormData'); |
|
176 |
+ if (savedData) { |
|
177 |
+ this.$data = JSON.parse(savedData); |
|
178 |
+ } |
|
179 |
+ }, |
|
180 |
+ }, |
|
181 |
+ mounted() { |
|
182 |
+ // Load the saved form data when the page is loaded |
|
183 |
+ this.loadFormData(); |
|
169 | 184 |
}, |
170 | 185 |
watch: { |
171 | 186 |
startDate: 'calculateDayCount', |
172 | 187 |
startTime: 'calculateDayCount', |
173 | 188 |
endDate: 'calculateDayCount', |
174 | 189 |
endTime: 'calculateDayCount', |
175 |
- reason: "validateForm", |
|
190 |
+ where: 'validateForm', |
|
191 |
+ purpose: "validateForm", |
|
176 | 192 |
}, |
177 | 193 |
}; |
178 | 194 |
</script> |
195 |
+ |
|
196 |
+<style scoped> |
|
197 |
+/* 필요한 스타일 추가 */ |
|
198 |
+</style> |
--- client/views/pages/Employee/ChuljangList.vue
+++ client/views/pages/Employee/ChuljangList.vue
... | ... | @@ -58,18 +58,18 @@ |
58 | 58 |
</thead> |
59 | 59 |
<!-- 동적으로 <td> 생성 --> |
60 | 60 |
<tbody> |
61 |
- <tr v-for="(item, index) in ChuljangData" :key="item.startDate + index"> |
|
61 |
+ <tr v-for="(item, index) in ChuljangFormData" :key=" index"> |
|
62 | 62 |
<td> |
63 | 63 |
<div class="form-check"> |
64 | 64 |
<label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> |
65 | 65 |
<input v-model="item.acceptTerms" class="form-check-input" type="checkbox" /> |
66 | 66 |
</div> |
67 | 67 |
</td> |
68 |
- <td><input type="date" v-model="item.startDate" /></td> |
|
69 |
- <td><input type="date" v-model="item.endDate" /></td> |
|
70 |
- <td><input type="text" v-model="item.where" /></td> |
|
71 |
- <td><input type="text" v-model="item.purpose" /></td> |
|
72 |
- <td ><input type="text" v-model="item.member" /> |
|
68 |
+ <td>{{ item.startDate }}</td> |
|
69 |
+ <td>{{ item.endDate }}</td> |
|
70 |
+ <td>{{ item.where }}</td> |
|
71 |
+ <td>{{ item.purpose }}</td> |
|
72 |
+ <td >{{ item.selectedMember }} |
|
73 | 73 |
|
74 | 74 |
</td> |
75 | 75 |
</tr> |
... | ... | @@ -94,11 +94,6 @@ |
94 | 94 |
months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
95 | 95 |
selectedYear: '', |
96 | 96 |
selectedMonth: '', |
97 |
- ChuljangData: [ |
|
98 |
- { startDate: '', endDate: '', where: '대구', purpose: '', acceptTerms: false }, |
|
99 |
- { startDate: '', endDate: '', where: '경산', acceptTerms: false }, |
|
100 |
- // 더 많은 데이터 추가... |
|
101 |
- ], |
|
102 | 97 |
filteredData: [], |
103 | 98 |
}; |
104 | 99 |
}, |
... | ... | @@ -152,6 +147,17 @@ |
152 | 147 |
this.currentPage = page; |
153 | 148 |
}, |
154 | 149 |
}, |
150 |
+ created() { |
|
151 |
+ // 로컬스토리지에서 기존 데이터가 있으면 불러오기 |
|
152 |
+ const storedData = localStorage.getItem('ChuljangFormData'); |
|
153 |
+ console.log(storedData); |
|
154 |
+ if (storedData) { |
|
155 |
+ // Parse the data and wrap it in an array |
|
156 |
+ const parsedData = JSON.parse(storedData); |
|
157 |
+ // Ensure the data is in an array format |
|
158 |
+ this.ChuljangFormData = Array.isArray(parsedData) ? parsedData : [parsedData]; |
|
159 |
+ } |
|
160 |
+}, |
|
155 | 161 |
mounted() { |
156 | 162 |
|
157 | 163 |
// 처음에는 모든 데이터를 표시 |
--- client/views/pages/Employee/HyugaInsert.vue
+++ client/views/pages/Employee/HyugaInsert.vue
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 |
<form class="row g-3 pt-3" @submit.prevent="handleSubmit"> |
12 | 12 |
<div class="col-md-9"> |
13 | 13 |
<label for="inputName5" class="form-label">구분</label> |
14 |
- <select id="inputState" class="form-select"> |
|
14 |
+ <select id="category" class="form-select" v-model="category"> |
|
15 | 15 |
<option selected>연차</option> |
16 | 16 |
<option>반차</option> |
17 | 17 |
<option>병가</option> |
... | ... | @@ -68,7 +68,7 @@ |
68 | 68 |
|
69 | 69 |
<div class="col-12"> |
70 | 70 |
<label for="prvonsh" class="form-label">사유</label> |
71 |
- <input type="text" class="form-control" id="prvonsh" v-model="reason" /> |
|
71 |
+ <input type="text" class="form-control" id="reason" v-model="reason" /> |
|
72 | 72 |
</div> |
73 | 73 |
|
74 | 74 |
<div class="text-end"> |
... | ... | @@ -92,8 +92,8 @@ |
92 | 92 |
startTime: "09:00", // 기본 시작 시간 09:00 |
93 | 93 |
endDate: today, |
94 | 94 |
endTime: "18:00", // 기본 종료 시간 18:00 |
95 |
- allDay: false, // 종일 여부 |
|
96 |
- dayCount: 1, // 사용 휴가일 계산 |
|
95 |
+ category: "", |
|
96 |
+ dayCount: 1, |
|
97 | 97 |
reason: "", // 사유 |
98 | 98 |
}; |
99 | 99 |
}, |
... | ... | @@ -109,6 +109,7 @@ |
109 | 109 |
validateForm() { |
110 | 110 |
// 필수 입력 필드 체크 |
111 | 111 |
if ( |
112 |
+ this.category && |
|
112 | 113 |
this.startDate && |
113 | 114 |
this.startTime && |
114 | 115 |
this.endDate && |
... | ... | @@ -151,12 +152,25 @@ |
151 | 152 |
handleSubmit() { |
152 | 153 |
this.validateForm(); // 제출 시 유효성 확인 |
153 | 154 |
if (this.isFormValid) { |
155 |
+ localStorage.setItem('HyugaFormData', JSON.stringify(this.$data)); |
|
154 | 156 |
alert("승인 요청이 완료되었습니다."); |
155 | 157 |
// 추가 처리 로직 (API 요청 등) |
156 | 158 |
} else { |
157 | 159 |
alert("모든 필드를 올바르게 작성해주세요."); |
158 | 160 |
} |
159 | 161 |
}, |
162 |
+ loadFormData() { |
|
163 |
+ const savedData = localStorage.getItem('HyugaFormData'); |
|
164 |
+ if (savedData) { |
|
165 |
+ this.$data = JSON.parse(savedData); |
|
166 |
+ } |
|
167 |
+ console.log(loadFormData) |
|
168 |
+ }, |
|
169 |
+ |
|
170 |
+ }, |
|
171 |
+ mounted() { |
|
172 |
+ // Load the saved form data when the page is loaded |
|
173 |
+ this.loadFormData(); |
|
160 | 174 |
}, |
161 | 175 |
watch: { |
162 | 176 |
startDate: 'calculateDayCount', |
... | ... | @@ -164,6 +178,7 @@ |
164 | 178 |
endDate: 'calculateDayCount', |
165 | 179 |
endTime: 'calculateDayCount', |
166 | 180 |
reason: "validateForm", |
181 |
+ category: 'category', |
|
167 | 182 |
}, |
168 | 183 |
}; |
169 | 184 |
</script> |
--- client/views/pages/Employee/HyugaList.vue
+++ client/views/pages/Employee/HyugaList.vue
... | ... | @@ -7,29 +7,18 @@ |
7 | 7 |
<div class="row"> |
8 | 8 |
<!-- 해당년도 연차 수 --> |
9 | 9 |
<!-- 전체 --> |
10 |
- <div class="col-xxl-2 col-md-3"> |
|
11 |
- <button type="button" class="btn btn-secondary mb-2"> |
|
10 |
+ <div class="col-xxl-2 col-md-3 mb-2"> |
|
11 |
+ <button type="button" class="btn btn-secondary "> |
|
12 | 12 |
전체 <span class="badge bg-white text-secondary">12개</span> |
13 |
- </button> |
|
14 |
- |
|
13 |
+ </button> |
|
14 |
+ <button type="button" class="btn btn-success "> |
|
15 |
+ 사용 <span class="badge bg-white text-success">1개</span> |
|
16 |
+ </button> |
|
17 |
+ <button type="button" class="btn btn-warning"> |
|
18 |
+ 미사용 <span class="badge bg-white text-warning">1개</span> |
|
19 |
+ </button> |
|
15 | 20 |
</div> |
16 | 21 |
<!-- End 전체 --> |
17 |
- |
|
18 |
- <!-- 사용 --> |
|
19 |
- <div class="col-xxl-2 col-md-3"> |
|
20 |
- <button type="button" class="btn btn-success mb-2"> |
|
21 |
- 사용 <span class="badge bg-white text-success">1개</span> |
|
22 |
- </button> |
|
23 |
- </div> |
|
24 |
- <!-- End 사용 --> |
|
25 |
- |
|
26 |
- <!-- 미사용 --> |
|
27 |
- <div class="col-xxl-2 col-xl-3"> |
|
28 |
- <button type="button" class="btn btn-warning mb-2"> |
|
29 |
- 미사용 <span class="badge bg-white text-warning">1개</span> |
|
30 |
- </button> |
|
31 |
- </div> |
|
32 |
- <!-- End 미사용 --> |
|
33 | 22 |
|
34 | 23 |
<div class="col-lg-12"> |
35 | 24 |
<div class="card"> |
... | ... | @@ -77,7 +66,7 @@ |
77 | 66 |
</thead> |
78 | 67 |
<!-- 동적으로 <td> 생성 --> |
79 | 68 |
<tbody> |
80 |
- <tr v-for="(item, index) in filteredData" :key="index"> |
|
69 |
+ <tr v-for="(item, index) in HyugaFormData" :key="index"> |
|
81 | 70 |
<td> |
82 | 71 |
<div class="form-check"> |
83 | 72 |
<label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> |
... | ... | @@ -87,7 +76,7 @@ |
87 | 76 |
<td>{{ item.category }}</td> |
88 | 77 |
<td>{{ item.startDate }}</td> |
89 | 78 |
<td>{{ item.endDate }}</td> |
90 |
- <td>{{ item.usedDays }}</td> |
|
79 |
+ <td>{{ item.dayCount }}</td> |
|
91 | 80 |
<td>{{ item.reason }}</td> |
92 | 81 |
<td>{{ item.approvalStatus ? '승인' : '대기' }}</td> |
93 | 82 |
</tr> |
... | ... | @@ -111,11 +100,6 @@ |
111 | 100 |
months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
112 | 101 |
selectedYear: '', |
113 | 102 |
selectedMonth: '', |
114 |
- vacationData: [ |
|
115 |
- { category: '연차', startDate: '2023-01-01', endDate: '2023-01-03', usedDays: 2, reason: '휴식', approvalStatus: true, acceptTerms: false }, |
|
116 |
- { category: '병가', startDate: '2024-02-10', endDate: '2024-02-12', usedDays: 2, reason: '병원', approvalStatus: false, acceptTerms: false }, |
|
117 |
- { category: '병가', startDate: '2025-01-10', endDate: '2024-02-12', usedDays: 2, reason: '병원', approvalStatus: false, acceptTerms: false }, |
|
118 |
- ], |
|
119 | 103 |
filteredData: [], |
120 | 104 |
}; |
121 | 105 |
}, |
... | ... | @@ -123,54 +107,64 @@ |
123 | 107 |
}, |
124 | 108 |
methods: { |
125 | 109 |
deletePending() { |
126 |
- // 선택된 항목만 필터링하여 삭제 |
|
127 |
- const selectedItems = this.vacationData.filter(item => item.acceptTerms); |
|
110 |
+ // 선택된 항목만 필터링하여 삭제 |
|
111 |
+ const selectedItems = this.vacationData.filter(item => item.acceptTerms); |
|
128 | 112 |
|
129 |
- // 승인된 항목인지 확인하여 삭제할 수 없으면 경고 |
|
130 |
- const nonDeletableItems = selectedItems.filter(item => item.approvalStatus === true); |
|
131 |
- |
|
132 |
- // 삭제할 수 없는 항목이 있으면 경고 |
|
133 |
- if (nonDeletableItems.length > 0) { |
|
134 |
- alert("승인된 건은 삭제할 수 없습니다."); |
|
135 |
- return; // 승인된 항목이 있으면 삭제를 진행하지 않음 |
|
136 |
- } |
|
113 |
+ // 승인된 항목인지 확인하여 삭제할 수 없으면 경고 |
|
114 |
+ const nonDeletableItems = selectedItems.filter(item => item.approvalStatus === true); |
|
137 | 115 |
|
138 |
- // 승인된 항목이 없으면 삭제 진행 |
|
139 |
- if (selectedItems.length > 0) { |
|
140 |
- this.vacationData = this.vacationData.filter(item => !item.acceptTerms); |
|
141 |
- alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); |
|
142 |
- } else { |
|
143 |
- alert("선택된 항목이 없습니다."); |
|
144 |
- } |
|
145 |
- }, |
|
116 |
+ // 삭제할 수 없는 항목이 있으면 경고 |
|
117 |
+ if (nonDeletableItems.length > 0) { |
|
118 |
+ alert("승인된 건은 삭제할 수 없습니다."); |
|
119 |
+ return; // 승인된 항목이 있으면 삭제를 진행하지 않음 |
|
120 |
+ } |
|
121 |
+ |
|
122 |
+ // 승인된 항목이 없으면 삭제 진행 |
|
123 |
+ if (selectedItems.length > 0) { |
|
124 |
+ this.vacationData = this.vacationData.filter(item => !item.acceptTerms); |
|
125 |
+ alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); |
|
126 |
+ } else { |
|
127 |
+ alert("선택된 항목이 없습니다."); |
|
128 |
+ } |
|
129 |
+ }, |
|
146 | 130 |
// 날짜 필터 적용 |
147 | 131 |
filterData() { |
148 |
- // 전체 선택 시 필터링 없이 모든 데이터를 표시 |
|
149 |
- if (!this.selectedYear && !this.selectedMonth) { |
|
150 |
- this.filteredData = this.vacationData; |
|
151 |
- } else { |
|
152 |
- // 선택된 연도와 월에 맞게 필터링 |
|
153 |
- this.filteredData = this.vacationData.filter(item => { |
|
154 |
- const itemYear = new Date(item.startDate).getFullYear(); |
|
155 |
- const itemMonth = new Date(item.startDate).getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줍니다. |
|
132 |
+ // 전체 선택 시 필터링 없이 모든 데이터를 표시 |
|
133 |
+ if (!this.selectedYear && !this.selectedMonth) { |
|
134 |
+ this.filteredData = this.vacationData; |
|
135 |
+ } else { |
|
136 |
+ // 선택된 연도와 월에 맞게 필터링 |
|
137 |
+ this.filteredData = this.vacationData.filter(item => { |
|
138 |
+ const itemYear = new Date(item.startDate).getFullYear(); |
|
139 |
+ const itemMonth = new Date(item.startDate).getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줍니다. |
|
156 | 140 |
|
157 |
- return ( |
|
158 |
- (!this.selectedYear || itemYear === parseInt(this.selectedYear)) && |
|
159 |
- (!this.selectedMonth || itemMonth === parseInt(this.selectedMonth)) |
|
160 |
- ); |
|
161 |
- }); |
|
162 |
- } |
|
163 |
- }, |
|
164 |
- |
|
141 |
+ return ( |
|
142 |
+ (!this.selectedYear || itemYear === parseInt(this.selectedYear)) && |
|
143 |
+ (!this.selectedMonth || itemMonth === parseInt(this.selectedMonth)) |
|
144 |
+ ); |
|
145 |
+ }); |
|
146 |
+ } |
|
147 |
+ }, |
|
148 |
+ |
|
165 | 149 |
|
166 | 150 |
}, |
151 |
+ created() { |
|
152 |
+ // 로컬스토리지에서 기존 데이터가 있으면 불러오기 |
|
153 |
+ const storedData = localStorage.getItem('HyugaFormData'); |
|
154 |
+ console.log(storedData); |
|
155 |
+ if (storedData) { |
|
156 |
+ // Parse the data and wrap it in an array |
|
157 |
+ const parsedData = JSON.parse(storedData); |
|
158 |
+ // Ensure the data is in an array format |
|
159 |
+ this.HyugaFormData = Array.isArray(parsedData) ? parsedData : [parsedData]; |
|
160 |
+ } |
|
161 |
+ }, |
|
167 | 162 |
mounted() { |
168 | 163 |
const currentYear = new Date().getFullYear(); |
169 |
- this.selectedYear = currentYear; |
|
164 |
+ this.selectedYear = currentYear; |
|
170 | 165 |
// 처음에는 현재년도 데이터를 표시 |
171 |
- this.filteredData = this.vacationData.filter(item => new Date(item.startDate).getFullYear() === currentYear); |
|
172 | 166 |
}, |
173 |
- |
|
167 |
+ |
|
174 | 168 |
}; |
175 | 169 |
</script> |
176 | 170 |
|
+++ client/views/pages/Employee/HyugaOk.vue
... | ... | @@ -0,0 +1,171 @@ |
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 mb-2"> | |
11 | + <button type="button" class="btn btn-secondary "> | |
12 | + 전체 <span class="badge bg-white text-secondary">12개</span> | |
13 | + </button> | |
14 | + <button type="button" class="btn btn-success "> | |
15 | + 사용 <span class="badge bg-white text-success">1개</span> | |
16 | + </button> | |
17 | + <button type="button" class="btn btn-warning"> | |
18 | + 미사용 <span class="badge bg-white text-warning">1개</span> | |
19 | + </button> | |
20 | + </div> | |
21 | + <!-- End 전체 --> | |
22 | + | |
23 | + <div class="col-lg-12"> | |
24 | + <div class="card"> | |
25 | + <div class="card-body"> | |
26 | + <h5 class="card-title">휴가내역</h5> | |
27 | + <div class="d-flex pb-3 justify-content-between "> | |
28 | + | |
29 | + <div class="datatable-search d-flex gap-1 "> | |
30 | + <div class="col-xxl-5 col-md-3"> | |
31 | + <select class="form-select " v-model="selectedYear"> | |
32 | + <option value="">전체</option> | |
33 | + <option v-for="year in years" :key="year" :value="year">{{ year }}</option> | |
34 | + </select> | |
35 | + </div> | |
36 | + <div class="col-xxl-5 col-md-3"> | |
37 | + <select class="form-select" v-model="selectedMonth"> | |
38 | + <option value="">월</option> | |
39 | + <option v-for="month in months" :key="month" :value="month">{{ month }}</option> | |
40 | + </select> | |
41 | + </div> | |
42 | + <button type="button" class="btn btn-outline-secondary col-xxl-5 col-xl-3" | |
43 | + @click="filterData">조회</button> | |
44 | + | |
45 | + </div> | |
46 | + <div class="d-flex justify-content-end "> | |
47 | + <button type="button" class="btn btn-outline-secondary" @click="deletePending"> | |
48 | + 삭제 | |
49 | + </button> | |
50 | + </div> | |
51 | + | |
52 | + </div> | |
53 | + <!-- Table --> | |
54 | + <table id="myTable" class="table datatable table-hover"> | |
55 | + <!-- 동적으로 <th> 생성 --> | |
56 | + <thead> | |
57 | + <tr> | |
58 | + <th>No</th> | |
59 | + <th>구분</th> | |
60 | + <th data-type="date" data-format="YYYY/DD/MM">시작일자</th> | |
61 | + <th data-type="date" data-format="YYYY/DD/MM">종료일자</th> | |
62 | + <th>사용일</th> | |
63 | + <th>사유</th> | |
64 | + <th>승인여부</th> | |
65 | + </tr> | |
66 | + </thead> | |
67 | + <!-- 동적으로 <td> 생성 --> | |
68 | + <tbody> | |
69 | + <tr v-for="(item, index) in HyugaFormData" :key="index"> | |
70 | + <td> | |
71 | + <div class="form-check"> | |
72 | + <label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> | |
73 | + <input v-model="item.acceptTerms" class="form-check-input" type="checkbox" /> | |
74 | + </div> | |
75 | + </td> | |
76 | + <td>{{ item.category }}</td> | |
77 | + <td>{{ item.startDate }}</td> | |
78 | + <td>{{ item.endDate }}</td> | |
79 | + <td>{{ item.dayCount }}</td> | |
80 | + <td>{{ item.reason }}</td> | |
81 | + <td>{{ item.approvalStatus ? '승인' : '대기' }}</td> | |
82 | + </tr> | |
83 | + </tbody> | |
84 | + </table> | |
85 | + <!-- End Table --> | |
86 | + </div> | |
87 | + </div> | |
88 | + </div> | |
89 | + </div> | |
90 | + </section> | |
91 | +</template> | |
92 | + | |
93 | +<script> | |
94 | +import { DataTable } from 'simple-datatables' | |
95 | +export default { | |
96 | + data() { | |
97 | + return { | |
98 | + // 데이터 초기화 | |
99 | + years: [2023, 2024, 2025], // 연도 목록 | |
100 | + months: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 | |
101 | + selectedYear: '', | |
102 | + selectedMonth: '', | |
103 | + filteredData: [], | |
104 | + }; | |
105 | + }, | |
106 | + computed: { | |
107 | + }, | |
108 | + methods: { | |
109 | + deletePending() { | |
110 | + // 선택된 항목만 필터링하여 삭제 | |
111 | + const selectedItems = this.vacationData.filter(item => item.acceptTerms); | |
112 | + | |
113 | + // 승인된 항목인지 확인하여 삭제할 수 없으면 경고 | |
114 | + const nonDeletableItems = selectedItems.filter(item => item.approvalStatus === true); | |
115 | + | |
116 | + // 삭제할 수 없는 항목이 있으면 경고 | |
117 | + if (nonDeletableItems.length > 0) { | |
118 | + alert("승인된 건은 삭제할 수 없습니다."); | |
119 | + return; // 승인된 항목이 있으면 삭제를 진행하지 않음 | |
120 | + } | |
121 | + | |
122 | + // 승인된 항목이 없으면 삭제 진행 | |
123 | + if (selectedItems.length > 0) { | |
124 | + this.vacationData = this.vacationData.filter(item => !item.acceptTerms); | |
125 | + alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); | |
126 | + } else { | |
127 | + alert("선택된 항목이 없습니다."); | |
128 | + } | |
129 | + }, | |
130 | + // 날짜 필터 적용 | |
131 | + filterData() { | |
132 | + // 전체 선택 시 필터링 없이 모든 데이터를 표시 | |
133 | + if (!this.selectedYear && !this.selectedMonth) { | |
134 | + this.filteredData = this.vacationData; | |
135 | + } else { | |
136 | + // 선택된 연도와 월에 맞게 필터링 | |
137 | + this.filteredData = this.vacationData.filter(item => { | |
138 | + const itemYear = new Date(item.startDate).getFullYear(); | |
139 | + const itemMonth = new Date(item.startDate).getMonth() + 1; // 월은 0부터 시작하므로 1을 더해줍니다. | |
140 | + | |
141 | + return ( | |
142 | + (!this.selectedYear || itemYear === parseInt(this.selectedYear)) && | |
143 | + (!this.selectedMonth || itemMonth === parseInt(this.selectedMonth)) | |
144 | + ); | |
145 | + }); | |
146 | + } | |
147 | + }, | |
148 | + | |
149 | + | |
150 | + }, | |
151 | + created() { | |
152 | + // 로컬스토리지에서 기존 데이터가 있으면 불러오기 | |
153 | + const storedData = localStorage.getItem('HyugaFormData'); | |
154 | + console.log(storedData); | |
155 | + if (storedData) { | |
156 | + // Parse the data and wrap it in an array | |
157 | + const parsedData = JSON.parse(storedData); | |
158 | + // Ensure the data is in an array format | |
159 | + this.HyugaFormData = Array.isArray(parsedData) ? parsedData : [parsedData]; | |
160 | + } | |
161 | + }, | |
162 | + mounted() { | |
163 | + const currentYear = new Date().getFullYear(); | |
164 | + this.selectedYear = currentYear; | |
165 | + // 처음에는 현재년도 데이터를 표시 | |
166 | + }, | |
167 | + | |
168 | +}; | |
169 | +</script> | |
170 | + | |
171 | +<style scoped></style> |
--- client/views/pages/Employee/ProjectInsert.vue
+++ client/views/pages/Employee/ProjectInsert.vue
... | ... | @@ -42,7 +42,7 @@ |
42 | 42 |
<form class="search-form d-flex align-items-center" method="POST" action="#" |
43 | 43 |
@submit.prevent="updateMember"> |
44 | 44 |
<input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword"> |
45 |
- <button type="submit" title="Search"><i class="bi bi-search"></i></button> |
|
45 |
+ <button type="submit" title="Search"><PlusOutlined /></button> |
|
46 | 46 |
</form> |
47 | 47 |
<input type="text" class="select-member" :value="selectedMember.join(' ')" > |
48 | 48 |
</div> |
... | ... | @@ -61,7 +61,7 @@ |
61 | 61 |
<form class="search-form d-flex align-items-center" method="POST" action="#" |
62 | 62 |
@submit.prevent="updateMember"> |
63 | 63 |
<input type="text" v-model="searchQuery" name="query" placeholder="Search" title="Enter search keyword"> |
64 |
- <button type="submit" title="Search"><i class="bi bi-search"></i></button> |
|
64 |
+ <button type="submit" title="Search"><PlusOutlined /></button> |
|
65 | 65 |
</form> |
66 | 66 |
<input type="text" class="select-member" :value="selectedMember.join(' ')" > |
67 | 67 |
</div> |
... | ... | @@ -89,6 +89,8 @@ |
89 | 89 |
|
90 | 90 |
<script> |
91 | 91 |
import { useAuthStore } from '../../stores/authStore'; |
92 |
+import { PlusOutlined } from '@ant-design/icons-vue'; |
|
93 |
+ |
|
92 | 94 |
export default { |
93 | 95 |
data() { |
94 | 96 |
const today = new Date().toISOString().split('T')[0]; |
... | ... | @@ -108,6 +110,9 @@ |
108 | 110 |
reason: "", // Notes or remarks |
109 | 111 |
}; |
110 | 112 |
}, |
113 |
+ components: { |
|
114 |
+ PlusOutlined, |
|
115 |
+ }, |
|
111 | 116 |
computed: { |
112 | 117 |
// Pinia Store의 상태를 가져옵니다. |
113 | 118 |
loginUser() { |
--- client/views/pages/Manager/NoticeInsert.vue
+++ client/views/pages/Manager/NoticeInsert.vue
... | ... | @@ -5,176 +5,183 @@ |
5 | 5 |
<section class="section"> |
6 | 6 |
<div class="card"> |
7 | 7 |
<div class="card-body pt-3"> |
8 |
- <table class="form-table" style="width: 100%;"> |
|
9 |
- <tbody> |
|
10 |
- <tr> |
|
11 |
- <th class="text-lf"> |
|
12 |
- <span>제목</span> |
|
13 |
- </th> |
|
14 |
- <td> |
|
15 |
- <input |
|
16 |
- type="text" |
|
17 |
- class="form-control" |
|
18 |
- placeholder="제목을 입력하세요." |
|
19 |
- /> |
|
20 |
- </td> |
|
21 |
- </tr> |
|
22 |
- <tr class="border-top"> |
|
23 |
- <th colspan="4" class="text-lf"> |
|
24 |
- <span>내용</span> |
|
25 |
- </th> |
|
26 |
- </tr> |
|
27 |
- <tr style="max-height: 600px"> |
|
28 |
- <td colspan="4" style="height: 100%" class="pt-0"> |
|
29 |
- <ckeditor :editor="editor" v-model="editorData"></ckeditor> |
|
30 |
- </td> |
|
31 |
- </tr> |
|
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 |
+ <ckeditor :editor="editor" required></ckeditor> |
|
27 |
+ <textarea name="" id="" v-model="editorData"></textarea> |
|
28 |
+ </td> |
|
29 |
+ </tr> |
|
32 | 30 |
|
33 |
- <!-- 첨부파일 --> |
|
34 |
- <tr class="border-top"> |
|
35 |
- <th class="text-lf"> |
|
36 |
- 첨부파일 |
|
37 |
- </th> |
|
38 |
- <td colspan="2"> |
|
39 |
- <div class="gd-12 pr0"> |
|
40 |
- <div class="gd-2 pl0 pr0"> |
|
41 |
- <label for="file" class="btn btn-outline-primary">파일찾기</label> |
|
42 |
- <input |
|
43 |
- type="file" |
|
44 |
- id="file" |
|
45 |
- ref="file" |
|
46 |
- @change="handleFileInsert" |
|
47 |
- multiple |
|
48 |
- /> |
|
31 |
+ <!-- 첨부파일 --> |
|
32 |
+ <tr class="border-top"> |
|
33 |
+ <th class="text-lf"> |
|
34 |
+ 첨부파일 |
|
35 |
+ </th> |
|
36 |
+ <td colspan="2"> |
|
37 |
+ <div class="gd-12 pr0"> |
|
38 |
+ <div class="gd-2 pl0 pr0"> |
|
39 |
+ <label for="file" class="btn btn-outline-primary">파일찾기</label> |
|
40 |
+ <input type="file" id="file" ref="file" multiple /> |
|
41 |
+ </div> |
|
49 | 42 |
</div> |
50 |
- </div> |
|
51 |
- </td> |
|
52 |
- </tr> |
|
43 |
+ </td> |
|
44 |
+ </tr> |
|
53 | 45 |
|
54 |
- <!-- 공지글 --> |
|
55 |
- <tr class="border-top"> |
|
56 |
- <th class="text-lf"> |
|
57 |
- 공지글 |
|
58 |
- </th> |
|
59 |
- <td colspan="3"> |
|
60 |
- <div class="d-flex no-gutters"> |
|
61 |
- <div class="col-md-4"> |
|
62 |
- <input |
|
63 |
- type="radio" |
|
64 |
- name="notice" |
|
65 |
- id="notice-y" |
|
66 |
- class="mr5" |
|
67 |
- value="Y" |
|
68 |
- /> |
|
69 |
- <label for="notice-y">사용</label> |
|
46 |
+ <!-- 공지글 --> |
|
47 |
+ <tr class="border-top"> |
|
48 |
+ <th class="text-lf"> |
|
49 |
+ 공지글 |
|
50 |
+ </th> |
|
51 |
+ <td colspan="3"> |
|
52 |
+ <div class="d-flex no-gutters"> |
|
53 |
+ <div class="col-md-4"> |
|
54 |
+ <input type="radio" name="notice" id="notice-y" class="mr5" value="Y" v-model="notice" required /> |
|
55 |
+ <label for="notice-y">사용</label> |
|
56 |
+ </div> |
|
57 |
+ <div class="col-md-4"> |
|
58 |
+ <input type="radio" name="notice" id="notice-n" class="mr5" value="N" v-model="notice" required /> |
|
59 |
+ <label for="notice-n">미사용</label> |
|
60 |
+ </div> |
|
70 | 61 |
</div> |
71 |
- <div class="col-md-4"> |
|
72 |
- <input |
|
73 |
- type="radio" |
|
74 |
- name="notice" |
|
75 |
- id="notice-n" |
|
76 |
- class="mr5" |
|
77 |
- value="N" |
|
78 |
- /> |
|
79 |
- <label for="notice-n">미사용</label> |
|
80 |
- </div> |
|
81 |
- </div> |
|
82 |
- </td> |
|
83 |
- </tr> |
|
62 |
+ </td> |
|
63 |
+ </tr> |
|
84 | 64 |
|
85 |
- <!-- 공지글 게시기간 --> |
|
86 |
- <tr class="border-top"> |
|
87 |
- <th class="text-lf"> |
|
88 |
- 공지글 게시기간 |
|
89 |
- </th> |
|
90 |
- <td colspan="3"> |
|
91 |
- <div class="d-flex no-gutters"> |
|
92 |
- <div class="col-md-4"> |
|
93 |
- <input |
|
94 |
- type="datetime-local" |
|
95 |
- class="form-control" |
|
96 |
- /> |
|
65 |
+ <!-- 공지글 게시기간 --> |
|
66 |
+ <tr class="border-top"> |
|
67 |
+ <th class="text-lf"> |
|
68 |
+ 공지글 게시기간 |
|
69 |
+ </th> |
|
70 |
+ <td colspan="3"> |
|
71 |
+ <div class="d-flex no-gutters"> |
|
72 |
+ <div class="col-md-4"> |
|
73 |
+ <input type="datetime-local" class="form-control" v-model="startDate" :disabled="notice === 'N'" |
|
74 |
+ required /> |
|
75 |
+ </div> |
|
76 |
+ <div class="pd-1">-</div> |
|
77 |
+ <div class="col-md-4"> |
|
78 |
+ <input type="datetime-local" class="form-control" v-model="endDate" :disabled="notice === 'N'" |
|
79 |
+ required /> |
|
80 |
+ </div> |
|
97 | 81 |
</div> |
98 |
- <div class="pd-1">-</div> |
|
99 |
- <div class="col-md-4"> |
|
100 |
- <input |
|
101 |
- type="datetime-local" |
|
102 |
- class="form-control" |
|
103 |
- /> |
|
104 |
- </div> |
|
105 |
- </div> |
|
106 |
- </td> |
|
107 |
- </tr> |
|
82 |
+ </td> |
|
83 |
+ </tr> |
|
84 |
+ </tbody> |
|
85 |
+ </table> |
|
86 |
+ <div class="text-end"> |
|
87 |
+ <button class="btn btn-primary" type="submit">등록</button> |
|
88 |
+ <button class="btn btn-secondary" @click="handleCancel">취소</button> |
|
89 |
+ </div> |
|
90 |
+ </form> |
|
108 | 91 |
|
109 |
- <!-- 비밀글 --> |
|
110 |
- <tr class="border-top"> |
|
111 |
- <th class="text-lf"> |
|
112 |
- 비밀글 |
|
113 |
- </th> |
|
114 |
- <td colspan="3"> |
|
115 |
- <div class="d-flex no-gutters"> |
|
116 |
- <div class="col-md-4"> |
|
117 |
- <input |
|
118 |
- type="radio" |
|
119 |
- name="private" |
|
120 |
- id="private-y" |
|
121 |
- class="mr5" |
|
122 |
- value="Y" |
|
123 |
- /> |
|
124 |
- <label for="private-y">사용</label> |
|
125 |
- </div> |
|
126 |
- <div class="col-md-4"> |
|
127 |
- <input |
|
128 |
- type="radio" |
|
129 |
- name="private" |
|
130 |
- id="private-n" |
|
131 |
- class="mr5" |
|
132 |
- value="N" |
|
133 |
- /> |
|
134 |
- <label for="private-n">미사용</label> |
|
135 |
- </div> |
|
136 |
- </div> |
|
137 |
- </td> |
|
138 |
- </tr> |
|
139 |
- </tbody> |
|
140 |
- </table> |
|
141 |
- <div class="text-end"> |
|
142 |
- <button class="btn btn-primary" @click="handleInsert"> |
|
143 |
- {{ notice == null ? "등록" : "수정" }} |
|
144 |
- </button> |
|
145 |
- <button class="btn btn-secondary" @click="handleCancel">취소</button> |
|
146 |
- </div> |
|
92 |
+ |
|
147 | 93 |
</div> |
148 | 94 |
</div> |
149 | 95 |
</section> |
150 | 96 |
</template> |
151 | 97 |
|
152 | 98 |
<script> |
153 |
- |
|
154 | 99 |
import CKEditor from '@ckeditor/ckeditor5-vue'; |
155 | 100 |
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; |
156 | 101 |
|
157 | 102 |
export default { |
158 | 103 |
components: { |
159 |
- ckeditor: CKEditor |
|
104 |
+ ckeditor: CKEditor, |
|
160 | 105 |
}, |
161 | 106 |
data() { |
107 |
+ console.log(localStorage.getItem('testKey')); |
|
108 |
+ const today = new Date().toISOString().split('T')[0]; |
|
109 |
+ const todayDatetime = new Date().toISOString().slice(0, 16); // To include time for datetime-local |
|
162 | 110 |
return { |
163 |
- editor: ClassicEditor, // Editor build to use |
|
164 |
- editorData: '', // Data for editor content |
|
111 |
+ editor: ClassicEditor, |
|
112 |
+ editorData: '', // Data for the editor |
|
113 |
+ title: '', // Bind to title input |
|
114 |
+ notice: '', // Bind to notice radio buttons |
|
115 |
+ startDate: todayDatetime, // Bind to start date |
|
116 |
+ endDate: todayDatetime, // Bind to end date |
|
117 |
+ isFormValid: true, // To track form validation status |
|
165 | 118 |
}; |
166 | 119 |
}, |
167 | 120 |
methods: { |
168 | 121 |
handleInsert() { |
169 |
- console.log('Editor Data:', this.editorData); |
|
122 |
+ console.log('Form Data:', { |
|
123 |
+ title: this.title, |
|
124 |
+ editorData: this.editorData, |
|
125 |
+ notice: this.notice, |
|
126 |
+ startDate: this.startDate, |
|
127 |
+ endDate: this.endDate, |
|
128 |
+ }); |
|
170 | 129 |
}, |
171 | 130 |
handleCancel() { |
172 |
- // Your cancel logic |
|
131 |
+ // Reset form fields |
|
132 |
+ this.title = ''; |
|
133 |
+ this.editorData = ''; |
|
134 |
+ this.notice = ''; |
|
135 |
+ this.startDate = this.endDate = new Date().toISOString().slice(0, 16); |
|
136 |
+ // Optionally remove from localStorage |
|
137 |
+ localStorage.removeItem('formData'); |
|
138 |
+ }, |
|
139 |
+ handleSubmit() { |
|
140 |
+ console.log('handleSubmit called'); |
|
141 |
+ // Validate the form before submission |
|
142 |
+ this.validateForm(); |
|
143 |
+ if (this.isFormValid) { |
|
144 |
+ // Save form data to localStorage |
|
145 |
+ const formData = { |
|
146 |
+ title: this.title, |
|
147 |
+ editorData: this.editorData, |
|
148 |
+ notice: this.notice, |
|
149 |
+ startDate: this.startDate, |
|
150 |
+ endDate: this.endDate, |
|
151 |
+ }; |
|
152 |
+ localStorage.setItem('formData', JSON.stringify(formData)); |
|
153 |
+ alert('등록되었습니다.'); |
|
154 |
+ // Add further logic here (e.g., API call) |
|
155 |
+ } else { |
|
156 |
+ alert('모든 필드를 올바르게 작성해주세요.'); |
|
157 |
+ } |
|
158 |
+ }, |
|
159 |
+ validateForm() { |
|
160 |
+ // Check if all required fields are filled |
|
161 |
+ this.isFormValid = !!( |
|
162 |
+ this.title && |
|
163 |
+ this.editorData && |
|
164 |
+ this.notice && |
|
165 |
+ this.startDate && |
|
166 |
+ this.endDate |
|
167 |
+ ); |
|
168 |
+ }, |
|
169 |
+ loadFormData() { |
|
170 |
+ const savedData = localStorage.getItem('formData'); |
|
171 |
+ console.log('savedData:', savedData); |
|
172 |
+ if (savedData) { |
|
173 |
+ const formData = JSON.parse(savedData); |
|
174 |
+ this.title = formData.title || ''; |
|
175 |
+ this.editorData = formData.editorData || ''; |
|
176 |
+ this.notice = formData.notice || ''; |
|
177 |
+ this.startDate = formData.startDate || ''; |
|
178 |
+ this.endDate = formData.endDate || ''; |
|
179 |
+ } |
|
173 | 180 |
}, |
174 | 181 |
}, |
175 | 182 |
mounted() { |
176 |
- console.log(CKEditor); // Check if CKEditor is properly imported and available |
|
177 |
-} |
|
183 |
+ this.loadFormData(); // Load saved data from localStorage |
|
184 |
+ }, |
|
178 | 185 |
}; |
179 | 186 |
</script> |
180 | 187 |
|
... | ... | @@ -183,9 +190,11 @@ |
183 | 190 |
th { |
184 | 191 |
padding: 1rem; |
185 | 192 |
} |
193 |
+ |
|
186 | 194 |
th { |
187 | 195 |
width: 10rem; |
188 | 196 |
} |
197 |
+ |
|
189 | 198 |
#file { |
190 | 199 |
position: absolute; |
191 | 200 |
width: 0; |
... | ... | @@ -194,8 +203,10 @@ |
194 | 203 |
overflow: hidden; |
195 | 204 |
border: 0; |
196 | 205 |
} |
206 |
+ |
|
197 | 207 |
.ckeditor { |
198 | 208 |
width: 100%; |
199 |
- height: 300px; /* Adjust height as needed */ |
|
209 |
+ height: 300px; |
|
210 |
+ /* Adjust height as needed */ |
|
200 | 211 |
} |
201 | 212 |
</style> |
--- client/views/pages/Manager/NoticeList.vue
+++ client/views/pages/Manager/NoticeList.vue
... | ... | @@ -64,7 +64,7 @@ |
64 | 64 |
</thead> |
65 | 65 |
<!-- 동적으로 <td> 생성 --> |
66 | 66 |
<tbody> |
67 |
- <tr v-for="(item, index) in ChuljangData" :key="item.startDate + index"> |
|
67 |
+ <tr v-for="(item, index) in filteredData" :key="item.startDate + index"> |
|
68 | 68 |
<td> |
69 | 69 |
<div class="form-check"> |
70 | 70 |
<label class="form-check-label" for="acceptTerms">{{ index + 1 }}</label> |
... | ... | @@ -88,7 +88,6 @@ |
88 | 88 |
</template> |
89 | 89 |
|
90 | 90 |
<script> |
91 |
-import { DataTable } from 'simple-datatables' |
|
92 | 91 |
export default { |
93 | 92 |
data() { |
94 | 93 |
return { |
... | ... | @@ -97,11 +96,6 @@ |
97 | 96 |
levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
98 | 97 |
selectedDept: '', |
99 | 98 |
selectedlevel: '', |
100 |
- ChuljangData: [ |
|
101 |
- { startDate: '', endDate: '', where: '대구', purpose: '', acceptTerms: false }, |
|
102 |
- { startDate: '', endDate: '', where: '경산', acceptTerms: false }, |
|
103 |
- // 더 많은 데이터 추가... |
|
104 |
- ], |
|
105 | 99 |
filteredData: [], |
106 | 100 |
}; |
107 | 101 |
}, |
... | ... | @@ -116,26 +110,55 @@ |
116 | 110 |
|
117 | 111 |
deletePending() { |
118 | 112 |
// 선택된 항목만 필터링하여 삭제 |
119 |
- const selectedItems = this.ChuljangData.filter(item => item.acceptTerms); |
|
113 |
+ const selectedItems = this.NoticegData.filter(item => item.acceptTerms); |
|
120 | 114 |
|
121 | 115 |
// 승인된 항목이 없으면 삭제 진행 |
122 | 116 |
if (selectedItems.length > 0) { |
123 |
- this.ChuljangData = this.ChuljangData.filter(item => !item.acceptTerms); |
|
117 |
+ this.NoticegData = this.NoticegData.filter(item => !item.acceptTerms); |
|
124 | 118 |
alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); |
125 | 119 |
} else { |
126 | 120 |
alert("선택된 항목이 없습니다."); |
127 | 121 |
} |
128 | 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 |
+ }, |
|
129 | 130 |
|
130 | 131 |
// 페이지 변경 |
131 | 132 |
changePage(page) { |
132 | 133 |
this.currentPage = page; |
133 | 134 |
}, |
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 |
+ }, |
|
148 |
+ 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 |
+ } |
|
134 | 157 |
}, |
135 | 158 |
mounted() { |
136 | 159 |
|
137 | 160 |
// 처음에는 모든 데이터를 표시 |
138 |
- this.filteredData = this.ChuljangData; |
|
161 |
+ this.filteredData = this.NoticegData; |
|
139 | 162 |
|
140 | 163 |
}, |
141 | 164 |
}; |
+++ client/views/pages/User/MyPage.vue
... | ... | @@ -0,0 +1,142 @@ |
1 | +<template> | |
2 | + <div class="container"> | |
3 | + | |
4 | + <div class="d-flex justify-content-center py-4"> | |
5 | + <a href="index.html" class="logo d-flex align-items-center w-auto"> | |
6 | + <!-- <span class="d-none d-lg-block"> <img :src="logo" alt=""></span> --> | |
7 | + </a> | |
8 | + </div><!-- End Logo --> | |
9 | + | |
10 | + <div class="card mb-3"> | |
11 | + | |
12 | + <div class="card-body"> | |
13 | + | |
14 | + <div class=" pb-2"> | |
15 | + <h2 class="card-title text-center pb-0 fs-4">마이페이지</h2> | |
16 | + </div> | |
17 | + | |
18 | + <form class="row g-3 needs-validation " :class="{ 'was-validated': formSubmitted }" @submit.prevent="handleRegister" novalidate> | |
19 | + <div class="col-12"> | |
20 | + <label for="yourName" class="form-label">이름</label> | |
21 | + <input v-model="name" type="text" name="name" class="form-control" id="yourName" required> | |
22 | + <div class="invalid-feedback">이름을 입력해주세요.</div> | |
23 | + </div> | |
24 | + <div class="col-12"> | |
25 | + <label for="yourdept" class="form-label">부서</label> | |
26 | + <select v-model="dept" id="inputState" class="form-select"> | |
27 | + <option value="" disabled selected>부서를 선택하세요</option> | |
28 | + <option v-for="(item, index) in DeptData" :key="index" :value="item.deptNM"> | |
29 | + {{ item.deptNM }} | |
30 | + </option> | |
31 | + </select> | |
32 | + </div> | |
33 | + <div class="col-12"> | |
34 | + <label for="yourlevel" class="form-label">직급</label> | |
35 | + <select v-model="level" id="yourlevel" class="form-select"> | |
36 | + <option value="" disabled selected>직급을 선택하세요</option> | |
37 | + <option value="level0">계약직(인턴)</option> | |
38 | + <option value="level1">사원</option> | |
39 | + <option value="level2">주임</option> | |
40 | + <option value="level3">대리</option> | |
41 | + <option value="level4">과장</option> | |
42 | + </select> | |
43 | + </div> | |
44 | + <div class="col-12"> | |
45 | + <label for="youremail" class="form-label">Email</label> | |
46 | + <div class="input-group has-validation"> | |
47 | + <input v-model="email" type="text" name="username" class="form-control" id="youremail" required readonly> | |
48 | + <div class="invalid-feedback">회사 이메일을 입력해주세요.</div> | |
49 | + </div> | |
50 | + </div> | |
51 | + | |
52 | + <div class="col-12"> | |
53 | + <label for="yourPassword" class="form-label">비밀번호 변경</label> | |
54 | + <input v-model="password" type="password" name="password" class="form-control" id="yourPassword" required > | |
55 | + <div class="invalid-feedback">비밀번호를 입력해주세요.</div> | |
56 | + </div> | |
57 | + | |
58 | + <div class="col-12"> | |
59 | + <div class="form-check"> | |
60 | + <!-- <input v-model="acceptTerms" class="form-check-input" name="terms" type="checkbox" value="" id="acceptTerms" required> --> | |
61 | + <!-- <label class="form-check-label" for="acceptTerms">이용약관에 동의합니다.</label> --> | |
62 | + <div class="invalid-feedback">이용약관에 동의하셔야 합니다.</div> | |
63 | + </div> | |
64 | + </div> | |
65 | + <div class="col-12"> | |
66 | + <button class="btn btn-primary w-100" type="submit">수정</button> | |
67 | + </div> | |
68 | + </form> | |
69 | + | |
70 | + </div> | |
71 | + </div> | |
72 | + </div> | |
73 | + | |
74 | + </template> | |
75 | + | |
76 | + <script> | |
77 | + export default { | |
78 | + data() { | |
79 | + return { | |
80 | + name: '', | |
81 | + email: '', | |
82 | + password: '', | |
83 | + dept: '', | |
84 | + level: '', | |
85 | + acceptTerms: false, | |
86 | + formSubmitted: false, | |
87 | + logo: "/client/resources/img/logo_t.png", // 경로를 Vue 프로젝트 구조에 맞게 설정 | |
88 | + }; | |
89 | + }, | |
90 | + methods: { | |
91 | + handleRegister() { | |
92 | + this.formSubmitted = true; | |
93 | + // 이메일과 비밀번호가 빈 값이 아니어야 한다 | |
94 | + if (!this.email.includes('@')) { | |
95 | + alert('이메일은 @를 포함해야 합니다.'); | |
96 | + return; // Stop further processing if email is invalid | |
97 | + } | |
98 | + | |
99 | + console.log('Email:', this.email); | |
100 | + console.log('Password:', this.password); | |
101 | + console.log('Name:', this.name); | |
102 | + console.log('Accept Terms:', this.acceptTerms); | |
103 | + if (this.email && this.password && this.name && this.acceptTerms && this.dept && this.level ) { | |
104 | + // 로컬 스토리지에 회원가입 정보 저장 | |
105 | + const userData = { | |
106 | + name: this.name, | |
107 | + email: this.email, | |
108 | + password: this.password, | |
109 | + dept: this.dept, | |
110 | + level: this.level, | |
111 | + }; | |
112 | + | |
113 | + console.log('User Data to be saved:', userData); | |
114 | + try { | |
115 | + localStorage.setItem("UserInfo", JSON.stringify(userData)); | |
116 | + alert('회원가입이 완료되었습니다!'); | |
117 | + | |
118 | + // Redirect to login page | |
119 | + this.$router.push("/login"); | |
120 | + } catch (error) { | |
121 | + console.error("Error saving to localStorage:", error); | |
122 | + alert("회원가입 중 오류가 발생했습니다."); | |
123 | + } | |
124 | + } else { | |
125 | + alert("모든 필드를 입력해주세요."); | |
126 | + } | |
127 | + }, | |
128 | + }, | |
129 | + created() { | |
130 | + // 로컬스토리지에서 데이터 불러오기 | |
131 | + const storedData = localStorage.getItem('DeptData'); | |
132 | + if (storedData) { | |
133 | + this.DeptData = JSON.parse(storedData); | |
134 | + } | |
135 | + }, | |
136 | +}; | |
137 | + </script> | |
138 | + | |
139 | + <style scoped> | |
140 | + | |
141 | + </style> | |
142 | + (파일 끝에 줄바꿈 문자 없음) |
+++ eslint.config.mjs
... | ... | @@ -0,0 +1,12 @@ |
1 | +import globals from "globals"; | |
2 | +import pluginJs from "@eslint/js"; | |
3 | +import pluginReact from "eslint-plugin-react"; | |
4 | + | |
5 | + | |
6 | +/** @type {import('eslint').Linter.Config[]} */ | |
7 | +export default [ | |
8 | + {files: ["**/*.{js,mjs,cjs,jsx}"]}, | |
9 | + {languageOptions: { globals: globals.browser }}, | |
10 | + pluginJs.configs.recommended, | |
11 | + pluginReact.configs.flat.recommended, | |
12 | +];(파일 끝에 줄바꿈 문자 없음) |
--- package-lock.json
+++ package-lock.json
This diff is too big to display. |
--- package.json
+++ package.json
... | ... | @@ -1,5 +1,7 @@ |
1 | 1 |
{ |
2 | 2 |
"dependencies": { |
3 |
+ "@ant-design/icons": "^5.6.1", |
|
4 |
+ "@ant-design/icons-vue": "^7.0.1", |
|
3 | 5 |
"@apollo/client": "^3.13.1", |
4 | 6 |
"@babel/cli": "7.19.3", |
5 | 7 |
"@babel/core": "7.19.3", |
... | ... | @@ -10,7 +12,9 @@ |
10 | 12 |
"@fullcalendar/daygrid": "^6.1.15", |
11 | 13 |
"@fullcalendar/vue3": "^6.1.15", |
12 | 14 |
"@vue/apollo-composable": "^4.2.1", |
15 |
+ "ant-design-vue": "^4.2.6", |
|
13 | 16 |
"babel-loader": "8.2.5", |
17 |
+ "bootstrap": "^5.3.3", |
|
14 | 18 |
"css-loader": "6.7.1", |
15 | 19 |
"express": "4.18.1", |
16 | 20 |
"file-loader": "6.2.0", |
... | ... | @@ -25,13 +29,13 @@ |
25 | 29 |
"realgrid": "^2.8.8", |
26 | 30 |
"rehackt": "^0.1.0", |
27 | 31 |
"simple-datatables": "^9.2.1", |
32 |
+ "typeorm": "^0.3.21", |
|
28 | 33 |
"url-loader": "4.1.1", |
29 | 34 |
"vue": "^3.5.13", |
30 | 35 |
"vue-apollo": "^3.1.2", |
31 | 36 |
"vue-cookies": "^1.8.6", |
32 | 37 |
"vue-loader": "^17.0.0", |
33 | 38 |
"vue-router": "4.1.5", |
34 |
- "vue-style-loader": "4.1.3", |
|
35 | 39 |
"vue3-sfc-loader": "^0.8.4", |
36 | 40 |
"vuex": "^4.1.0", |
37 | 41 |
"webpack": "5.74.0", |
... | ... | @@ -46,5 +50,14 @@ |
46 | 50 |
"linux-dev": "export NODE_ENV=development&&node ./server/modules/web/server.js", |
47 | 51 |
"webpack-build": "webpack", |
48 | 52 |
"webpack-build-watch": "webpack --watch" |
53 |
+ }, |
|
54 |
+ "devDependencies": { |
|
55 |
+ "@eslint/js": "^9.21.0", |
|
56 |
+ "eslint": "^9.21.0", |
|
57 |
+ "eslint-plugin-react": "^7.37.4", |
|
58 |
+ "globals": "^16.0.0", |
|
59 |
+ "sass": "^1.85.1", |
|
60 |
+ "sass-loader": "^16.0.5", |
|
61 |
+ "vue-style-loader": "^4.1.3" |
|
49 | 62 |
} |
50 | 63 |
} |
--- webpack.config.js
+++ webpack.config.js
... | ... | @@ -1,7 +1,7 @@ |
1 | 1 |
const path = require('path'); |
2 | 2 |
const { VueLoaderPlugin } = require("vue-loader"); |
3 | 3 |
|
4 |
-const {PROJECT_NAME, BASE_DIR, SERVICE_STATUS} = require('./Global'); |
|
4 |
+const { PROJECT_NAME, BASE_DIR, SERVICE_STATUS } = require('./Global'); |
|
5 | 5 |
|
6 | 6 |
module.exports = { |
7 | 7 |
name: PROJECT_NAME, |
... | ... | @@ -13,38 +13,49 @@ |
13 | 13 |
}, |
14 | 14 |
|
15 | 15 |
module: { |
16 |
- rules: [{ |
|
17 |
- test: /\.vue?$/, |
|
18 |
- loader: 'vue-loader', |
|
19 |
- }, { |
|
20 |
- test: /\.(js|jsx)?$/, |
|
21 |
- loader: 'babel-loader', |
|
22 |
- }, |
|
23 |
- { |
|
24 |
- test: /\.css$/, |
|
25 |
- use: ['vue-style-loader', 'css-loader'] |
|
26 |
- }, |
|
27 |
- { |
|
28 |
- test: /\.(jpe?g|png|gif|svg|ttf|eot|woff|woff2)$/i, |
|
29 |
- use: [{ |
|
30 |
- loader:'url-loader', |
|
31 |
- options:{ |
|
32 |
- limit:8192, |
|
33 |
- fallback:require.resolve('file-loader') |
|
34 |
- } |
|
35 |
- }] |
|
36 |
- }], |
|
16 |
+ rules: [ |
|
17 |
+ { |
|
18 |
+ test: /\.vue$/, |
|
19 |
+ loader: 'vue-loader', |
|
20 |
+ }, |
|
21 |
+ { |
|
22 |
+ test: /\.(js|jsx)$/, |
|
23 |
+ loader: 'babel-loader', |
|
24 |
+ }, |
|
25 |
+ { |
|
26 |
+ test: /\.(css|scss)$/, |
|
27 |
+ use: [ |
|
28 |
+ 'vue-style-loader', // Injects styles into the DOM |
|
29 |
+ 'css-loader', // Resolves CSS imports |
|
30 |
+ 'sass-loader' // Compiles SCSS to CSS |
|
31 |
+ ] |
|
32 |
+ }, |
|
33 |
+ { |
|
34 |
+ test: /\.(jpe?g|png|gif|svg|ttf|eot|woff|woff2)$/i, |
|
35 |
+ use: [{ |
|
36 |
+ loader: 'url-loader', |
|
37 |
+ options: { |
|
38 |
+ limit: 8192, // Images smaller than 8KB will be inlined as base64 |
|
39 |
+ fallback: require.resolve('file-loader') // For larger images, fall back to file-loader |
|
40 |
+ } |
|
41 |
+ }] |
|
42 |
+ } |
|
43 |
+ ], |
|
37 | 44 |
}, |
38 | 45 |
|
39 |
- plugins: [new VueLoaderPlugin()], |
|
46 |
+ plugins: [ |
|
47 |
+ new VueLoaderPlugin(), // Ensures vue files are properly compiled |
|
48 |
+ ], |
|
40 | 49 |
|
41 | 50 |
output: { |
42 |
- path: `${BASE_DIR}/client/build`, // __dirname: webpack.config.js 파일이 위치한 경로 |
|
43 |
- filename: 'bundle.js' |
|
51 |
+ path: path.resolve(BASE_DIR, 'client/build'), // Absolute path for the output folder |
|
52 |
+ filename: 'bundle.js', // The name of the output JavaScript bundle |
|
44 | 53 |
}, |
54 |
+ |
|
45 | 55 |
resolve: { |
46 | 56 |
alias: { |
47 | 57 |
'realgrid': path.resolve(__dirname, 'node_modules/realgrid/dist/realgrid.js'), |
48 | 58 |
}, |
59 |
+ extensions: ['.js', '.vue', '.json', '.scss'] // Added '.scss' for convenience |
|
49 | 60 |
}, |
50 |
-}(파일 끝에 줄바꿈 문자 없음) |
|
61 |
+}; |
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?