
+++ client/views/component/GoogleCalendar.vue
... | ... | @@ -0,0 +1,114 @@ |
1 | +<template> | |
2 | + <div> | |
3 | + <FullCalendar :options="calendarOptions" /> | |
4 | + </div> | |
5 | + </template> | |
6 | + | |
7 | + <script> | |
8 | + import { ref, onMounted } from 'vue'; | |
9 | + import FullCalendar from '@fullcalendar/vue3'; | |
10 | + import dayGridPlugin from '@fullcalendar/daygrid'; | |
11 | + | |
12 | + | |
13 | + | |
14 | + // gapi 스크립트 로드 함수 | |
15 | + function loadGapiScript() { | |
16 | + return new Promise((resolve, reject) => { | |
17 | + const script = document.createElement('script'); | |
18 | + script.src = 'https://apis.google.com/js/api.js'; | |
19 | + script.onload = resolve; // 로드 성공 | |
20 | + script.onerror = reject; // 로드 실패 | |
21 | + document.head.appendChild(script); | |
22 | + }); | |
23 | + } | |
24 | + | |
25 | + export default { | |
26 | + name: 'GoogleCalendar', | |
27 | + components: { | |
28 | + FullCalendar | |
29 | + }, | |
30 | + setup() { | |
31 | + const events = ref([]); | |
32 | + const calendarOptions = ref({ | |
33 | + plugins: [dayGridPlugin], | |
34 | + initialView: 'dayGridMonth', | |
35 | + events: [], | |
36 | + titleFormat: { // 월 제목을 원하는 형식으로 변경 | |
37 | + month: 'long', // 'long' 은 'February' 형식으로 | |
38 | + year: 'numeric', // '2025' 형식으로 | |
39 | + day: 'numeric' // '4' 형식으로 | |
40 | + }, | |
41 | + locale: 'ko', // 한국어로 설정 | |
42 | + headerToolbar: { | |
43 | + left: 'prev,next today', | |
44 | + center: 'title', | |
45 | + right: 'dayGridMonth,dayGridWeek,dayGridDay' | |
46 | + }, | |
47 | + buttonText: { | |
48 | + today: '오늘', | |
49 | + month: '월', | |
50 | + week: '주', | |
51 | + day: '일' | |
52 | + } | |
53 | + }); | |
54 | + | |
55 | + const loadClient = () => { | |
56 | + if (window.gapi) { | |
57 | + window.gapi.client.init({ | |
58 | + apiKey: 'AIzaSyCNthSbTXgMrCG_dbuIhnk9BEVlp0ME5gM', // 공개 캘린더에 접근하기 위한 API Key | |
59 | + discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'], | |
60 | + }).then(() => { | |
61 | + loadCalendarEvents(); // 공개 캘린더 이벤트 불러오기 | |
62 | + }).catch(error => { | |
63 | + console.error('Error initializing gapi client:', error); | |
64 | + }); | |
65 | + } else { | |
66 | + console.error('gapi is not defined'); | |
67 | + } | |
68 | + }; | |
69 | + | |
70 | + const loadCalendarEvents = () => { | |
71 | + window.gapi.client.calendar.events.list({ | |
72 | + calendarId: 'ko.south_korea#holiday@group.v.calendar.google.com', // 공개 캘린더 ID | |
73 | + timeMin: new Date().toISOString(), // 현재 시점 이후의 이벤트 가져오기 | |
74 | + showDeleted: false, | |
75 | + singleEvents: true, | |
76 | + orderBy: 'startTime', | |
77 | + }).then((response) => { | |
78 | + events.value = response.result.items; | |
79 | + calendarOptions.value.events = events.value.map(event => ({ | |
80 | + title: event.summary, | |
81 | + start: event.start.dateTime || event.start.date, | |
82 | + end: event.end.dateTime || event.end.date | |
83 | + })); | |
84 | + }).catch(error => { | |
85 | + console.error('Error loading calendar events:', error); | |
86 | + }); | |
87 | + }; | |
88 | + | |
89 | + onMounted(async () => { | |
90 | + try { | |
91 | + await loadGapiScript(); | |
92 | + if (window.gapi) { | |
93 | + window.gapi.load('client', loadClient); | |
94 | + } else { | |
95 | + console.error('gapi is not defined after loading script'); | |
96 | + } | |
97 | + } catch (error) { | |
98 | + console.error('Error loading gapi script:', error); | |
99 | + } | |
100 | + }); | |
101 | + | |
102 | + return { | |
103 | + calendarOptions | |
104 | + }; | |
105 | + }, | |
106 | + }; | |
107 | + </script> | |
108 | + | |
109 | + <style scoped> | |
110 | +/* FullCalendar 스타일을 CDN을 통해 불러오기 */ | |
111 | +@import url('https://cdn.jsdelivr.net/npm/@fullcalendar/core@3.10.2/main.css'); | |
112 | +@import url('https://cdn.jsdelivr.net/npm/@fullcalendar/daygrid@3.10.2/main.css'); | |
113 | +</style> | |
114 | + (파일 끝에 줄바꿈 문자 없음) |
+++ client/views/component/ckeditor5/ckeditorComponent.vue
... | ... | @@ -0,0 +1,138 @@ |
1 | +<template> | |
2 | + <textarea name="editor5" id="editor5" class="form-control" style="width:100%" ref="editorContainer"></textarea> | |
3 | + </template> | |
4 | + | |
5 | + <script> | |
6 | + | |
7 | + export default { | |
8 | + props: { | |
9 | + bbsCn: { | |
10 | + type: Object, | |
11 | + required: true | |
12 | + } | |
13 | + }, | |
14 | + setup(props) { | |
15 | + const editorContainer = ref(null); | |
16 | + let editor = null; | |
17 | + | |
18 | + const createEditor = () => { | |
19 | + ClassicEditor | |
20 | + .create(editorContainer.value, { | |
21 | + extraPlugins: [MyCustomUploadAdapterPlugin], | |
22 | + removePlugins: ['MediaEmbed', 'MediaEmbedToolbar'], | |
23 | + image: { | |
24 | + toolbar: [ | |
25 | + 'imageTextAlternative', | |
26 | + '|', | |
27 | + 'imageStyle:alignLeft', | |
28 | + 'imageStyle:alignCenter', | |
29 | + 'imageStyle:alignRight', | |
30 | + '|', | |
31 | + 'resizeImage:50', | |
32 | + 'resizeImage:75', | |
33 | + 'resizeImage:original', | |
34 | + 'resizeImage:custom', | |
35 | + ], | |
36 | + resizeOptions: [ | |
37 | + { | |
38 | + name: 'resizeImage:original', | |
39 | + value: null, | |
40 | + icon: 'original' | |
41 | + }, | |
42 | + { | |
43 | + name: 'resizeImage:custom', | |
44 | + value: 'custom', | |
45 | + icon: 'custom' | |
46 | + }, | |
47 | + { | |
48 | + name: 'resizeImage:50', | |
49 | + value: '50', | |
50 | + icon: 'medium' | |
51 | + }, | |
52 | + { | |
53 | + name: 'resizeImage:75', | |
54 | + value: '75', | |
55 | + icon: 'large' | |
56 | + } | |
57 | + ], | |
58 | + }, | |
59 | + fontFamily: { | |
60 | + options: [ | |
61 | + 'default', | |
62 | + '궁서체', | |
63 | + '바탕', | |
64 | + '돋움', | |
65 | + "Arial, Helvetica, sans-serif", | |
66 | + "Courier New, Courier, monospace", | |
67 | + "Georgia, serif", | |
68 | + "Lucida Sans Unicode, Lucida Grande, sans-serif", | |
69 | + "Tahoma, Geneva, sans-serif", | |
70 | + "Times New Roman, Times, serif", | |
71 | + "Trebuchet MS, Helvetica, sans-serif", | |
72 | + "Verdana, Geneva, sans-serif", | |
73 | + ], | |
74 | + }, | |
75 | + table: { | |
76 | + contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableCellProperties'], | |
77 | + tableProperties: { | |
78 | + defaultProperties: { | |
79 | + borderStyle: 'solid', | |
80 | + borderColor: 'hsl(0, 0%, 0%)', | |
81 | + borderWidth: '1px', | |
82 | + } | |
83 | + }, | |
84 | + tableCellProperties: { | |
85 | + defaultProperties: { | |
86 | + borderStyle: 'solid', | |
87 | + borderColor: 'hsl(0, 0%, 0%)', | |
88 | + borderWidth: '1px', | |
89 | + } | |
90 | + } | |
91 | + } | |
92 | + }) | |
93 | + .then(editorInstance => { | |
94 | + editor = editorInstance; | |
95 | + editor.setData(props.bbsCn.bbsCn); | |
96 | + editor.model.document.on('change', () => { | |
97 | + props.bbsCn.bbsCn = editor.getData(); | |
98 | + }); | |
99 | + }) | |
100 | + .catch(error => { | |
101 | + console.error('There was a problem initializing the editor.', error); | |
102 | + }); | |
103 | + }; | |
104 | + | |
105 | + const MyCustomUploadAdapterPlugin = (editor) => { | |
106 | + editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { | |
107 | + return new UploadAdapter(loader); | |
108 | + }; | |
109 | + }; | |
110 | + | |
111 | + onMounted(() => { | |
112 | + createEditor(); // Ensure the editor is created when the component is mounted | |
113 | + }); | |
114 | + | |
115 | + onBeforeUnmount(() => { // Changed from beforeDestroy to onBeforeUnmount for Vue 3 | |
116 | + if (editor) { | |
117 | + editor.destroy() | |
118 | + .then(() => { | |
119 | + editor = null; | |
120 | + }); | |
121 | + } | |
122 | + }); | |
123 | + | |
124 | + return { | |
125 | + editorContainer, | |
126 | + }; | |
127 | + }, | |
128 | + } | |
129 | + </script> | |
130 | + | |
131 | + <style scoped> | |
132 | + .editor-container { | |
133 | + min-height: 300px; /* 에디터 컨테이너의 최소 높이 설정 */ | |
134 | + border: 1px solid #ccc; /* 에디터 컨테이너의 테두리 설정 */ | |
135 | + } | |
136 | + /* 필요에 따라 스타일 추가 */ | |
137 | + </style> | |
138 | + (파일 끝에 줄바꿈 문자 없음) |
--- client/views/index.js
+++ client/views/index.js
... | ... | @@ -1,14 +1,54 @@ |
1 |
-/** |
|
2 |
- * @author : 최정우 |
|
3 |
- * @since : 2022.10.19 |
|
4 |
- * @dscription : Vue를 활용한 Client단 구현의 시작점(Index) Component 입니다. |
|
5 |
- */ |
|
6 | 1 |
import { createApp } from 'vue'; |
7 |
-import AppRouter from './pages/AppRouter.js'; |
|
2 |
+import AppRouter from './pages/AppRouter.js'; // AppRouter.js에서 default export한 router를 가져옵니다. |
|
8 | 3 |
import App from './pages/App.vue'; |
9 |
-import { createPinia } from 'pinia'; |
|
4 |
+import Store from "./pages/AppStore.js"; |
|
5 |
+import VueCookies from 'vue-cookies'; // 쿠키 플러그인 추가 |
|
10 | 6 |
|
11 |
-createApp(App) |
|
12 |
- .use(createPinia()) |
|
13 |
- .use(AppRouter) |
|
14 |
- .mount('#root');(파일 끝에 줄바꿈 문자 없음) |
|
7 |
+// Google API 스크립트 동적 로드 함수 |
|
8 |
+function loadGapiScript() { |
|
9 |
+ return new Promise((resolve, reject) => { |
|
10 |
+ const script = document.createElement('script'); |
|
11 |
+ script.src = 'https://apis.google.com/js/api.js'; |
|
12 |
+ script.onload = resolve; // 로드 성공 |
|
13 |
+ script.onerror = reject; // 로드 실패 |
|
14 |
+ document.head.appendChild(script); |
|
15 |
+ }); |
|
16 |
+} |
|
17 |
+ |
|
18 |
+// Google API 로드 후 Vue 앱 초기화 |
|
19 |
+async function initVueApp() { |
|
20 |
+ try { |
|
21 |
+ // gapi 스크립트를 로드합니다. |
|
22 |
+ await loadGapiScript(); |
|
23 |
+ |
|
24 |
+ // gapi가 정상적으로 로드되었는지 확인합니다. |
|
25 |
+ if (window.gapi) { |
|
26 |
+ window.gapi.load('client:auth2', initVue); // Vue 앱 초기화 함수 호출 |
|
27 |
+ } else { |
|
28 |
+ console.error('gapi is not defined'); |
|
29 |
+ } |
|
30 |
+ } catch (error) { |
|
31 |
+ console.error("Google API 로드 실패:", error); |
|
32 |
+ } |
|
33 |
+} |
|
34 |
+ |
|
35 |
+// Vue 앱 초기화 함수 |
|
36 |
+function initVue() { |
|
37 |
+ const router = AppRouter; // AppRouter를 바로 사용합니다. |
|
38 |
+ const vueApp = createApp(App); // Vue 앱 인스턴스를 생성합니다. |
|
39 |
+ |
|
40 |
+ // Vue Router와 Store 사용 |
|
41 |
+ vueApp |
|
42 |
+ .use(router) // Vue Router 사용 |
|
43 |
+ .use(Store) // Vuex store 사용 |
|
44 |
+ .use(VueCookies) // VueCookies 사용 |
|
45 |
+ .config.globalProperties.$gapi = window.gapi; // gapi를 전역 속성으로 설정 |
|
46 |
+ |
|
47 |
+ // 쿠키 설정 |
|
48 |
+ vueApp.$cookies.config("1d"); // 쿠키 만료일 설정 |
|
49 |
+ |
|
50 |
+ vueApp.mount("#root"); // Vue 앱을 #root에 마운트 |
|
51 |
+} |
|
52 |
+ |
|
53 |
+// 앱 초기화 함수 실행 |
|
54 |
+initVueApp(); |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -20,6 +20,7 @@ |
20 | 20 |
import DeptList from '../pages/Manager/DeptList.vue'; |
21 | 21 |
import EmployeeList from '../pages/Manager/EmployeeList.vue'; |
22 | 22 |
import NoticeList from '../pages/Manager/NoticeList.vue'; |
23 |
+import NoticeInsert from '../pages/Manager/NoticeInsert.vue'; |
|
23 | 24 |
import PubHoliyday from '../pages/Manager/PubHoliyday.vue'; |
24 | 25 |
|
25 | 26 |
const routes = [ |
... | ... | @@ -37,6 +38,7 @@ |
37 | 38 |
{ path: '/DeptList', name: 'DeptList', component: DeptList }, |
38 | 39 |
{ path: '/EmployeeList', name: 'EmployeeList', component: EmployeeList }, |
39 | 40 |
{ path: '/NoticeList', name: 'NoticeList', component: NoticeList }, |
41 |
+ { path: '/NoticeInsert', name: 'NoticeInsert', component: NoticeInsert }, |
|
40 | 42 |
{ path: '/PubHoliyday', name: 'PubHoliyday', component: PubHoliyday }, |
41 | 43 |
]; |
42 | 44 |
|
+++ client/views/pages/AppStore.js
... | ... | @@ -0,0 +1,38 @@ |
1 | +import { createStore } from 'vuex'; | |
2 | + | |
3 | +const store = createStore({ | |
4 | + // 애플리케이션의 상태(state)를 정의합니다 | |
5 | + state: { | |
6 | + count: 0, | |
7 | + }, | |
8 | + // 상태를 변경하는 방법을 정의하는 mutations | |
9 | + mutations: { | |
10 | + increment(state) { | |
11 | + state.count++; | |
12 | + }, | |
13 | + decrement(state) { | |
14 | + state.count--; | |
15 | + }, | |
16 | + }, | |
17 | + // 액션을 통해 비동기적으로 상태를 변경하는 방법을 정의하는 actions | |
18 | + actions: { | |
19 | + incrementAsync({ commit }) { | |
20 | + setTimeout(() => { | |
21 | + commit('increment'); | |
22 | + }, 1000); | |
23 | + }, | |
24 | + decrementAsync({ commit }) { | |
25 | + setTimeout(() => { | |
26 | + commit('decrement'); | |
27 | + }, 1000); | |
28 | + }, | |
29 | + }, | |
30 | + // computed 속성과 비슷한 역할을 하는 getters를 정의 | |
31 | + getters: { | |
32 | + getCount(state) { | |
33 | + return state.count; | |
34 | + }, | |
35 | + }, | |
36 | +}); | |
37 | + | |
38 | +export default store; |
--- client/views/pages/Employee/ChuljangList.vue
+++ client/views/pages/Employee/ChuljangList.vue
... | ... | @@ -31,13 +31,13 @@ |
31 | 31 |
|
32 | 32 |
</div> |
33 | 33 |
<div class="d-flex justify-content-end "> |
34 |
- <button type="button" class="btn btn-outline-secondary" @click="registerLeave"> |
|
34 |
+ <button type="button" class="btn btn-outline-primary" @click="registerLeave"> |
|
35 | 35 |
등록 |
36 | 36 |
</button> |
37 | 37 |
<button type="button" class="btn btn-outline-success" @click="saveChanges"> |
38 | 38 |
저장 |
39 | 39 |
</button> |
40 |
- <button type="button" class="btn btn-outline-danger" @click="deletePending"> |
|
40 |
+ <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
41 | 41 |
삭제 |
42 | 42 |
</button> |
43 | 43 |
</div> |
--- client/views/pages/Employee/HyugaList.vue
+++ client/views/pages/Employee/HyugaList.vue
... | ... | @@ -55,7 +55,7 @@ |
55 | 55 |
|
56 | 56 |
</div> |
57 | 57 |
<div class="d-flex justify-content-end "> |
58 |
- <button type="button" class="btn btn-outline-danger" @click="deletePending"> |
|
58 |
+ <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
59 | 59 |
삭제 |
60 | 60 |
</button> |
61 | 61 |
</div> |
--- client/views/pages/Employee/ProjectList.vue
+++ client/views/pages/Employee/ProjectList.vue
... | ... | @@ -54,10 +54,10 @@ |
54 | 54 |
|
55 | 55 |
</div> |
56 | 56 |
<div class="d-flex justify-content-end "> |
57 |
- <button type="button" class="btn btn-outline-success"> |
|
58 |
- 수정 |
|
57 |
+ <button type="button" class="btn btn-outline-primary"> |
|
58 |
+ 등록 |
|
59 | 59 |
</button> |
60 |
- <button type="button" class="btn btn-outline-danger" @click="deletePending"> |
|
60 |
+ <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
61 | 61 |
삭제 |
62 | 62 |
</button> |
63 | 63 |
</div> |
--- client/views/pages/Manager/DeptList.vue
+++ client/views/pages/Manager/DeptList.vue
... | ... | @@ -14,13 +14,13 @@ |
14 | 14 |
<div class="d-flex pb-3 justify-content-end "> |
15 | 15 |
|
16 | 16 |
<div class="d-flex justify-content-end "> |
17 |
- <button type="button" class="btn btn-outline-secondary" @click="registerLeave"> |
|
17 |
+ <button type="button" class="btn btn-outline-primary" @click="registerLeave"> |
|
18 | 18 |
등록 |
19 | 19 |
</button> |
20 | 20 |
<button type="button" class="btn btn-outline-success" @click="saveChanges"> |
21 | 21 |
저장 |
22 | 22 |
</button> |
23 |
- <button type="button" class="btn btn-outline-danger" @click="deletePending"> |
|
23 |
+ <button type="button" class="btn btn-outline-secondary" @click="deletePending"> |
|
24 | 24 |
삭제 |
25 | 25 |
</button> |
26 | 26 |
</div> |
+++ client/views/pages/Manager/NoticeInsert.vue
... | ... | @@ -0,0 +1,351 @@ |
1 | +<template> | |
2 | + <div class="pagetitle"> | |
3 | + <h1>공지사항 등록</h1> | |
4 | + </div> | |
5 | + <section class="section"> | |
6 | + <div class="card"> | |
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 | + v-model="notice.title" | |
19 | + placeholder="제목을 입력하세요." | |
20 | + /> | |
21 | + </td> | |
22 | + </tr> | |
23 | + <tr class="border-top"> | |
24 | + <th colspan="4" class="text-lf"> | |
25 | + <span>내용</span> | |
26 | + </th> | |
27 | + </tr> | |
28 | + <tr style="max-height: 600px"> | |
29 | + <td colspan="4" style="height: 100%" class="pt-0"> | |
30 | + <ckeditorComponent | |
31 | + ref="ckeditor5" | |
32 | + :bbsCn.sync="bbsCn" | |
33 | + ></ckeditorComponent> | |
34 | + </td> | |
35 | + </tr> | |
36 | + | |
37 | + <!-- 첨부파일 --> | |
38 | + <tr class="border-top"> | |
39 | + <th class="text-lf"> | |
40 | + 첨부파일 | |
41 | + </th> | |
42 | + <td colspan="2"> | |
43 | + <div class="gd-12 pr0"> | |
44 | + <div class="gd-2 pl0 pr0"> | |
45 | + <label for="file" class="btn btn-outline-primary">파일찾기</label> | |
46 | + <input | |
47 | + type="file" | |
48 | + id="file" | |
49 | + ref="file" | |
50 | + @change="handleFileInsert" | |
51 | + multiple | |
52 | + /> | |
53 | + </div> | |
54 | + <div class="gd-12 pl0 pr0" v-if="fileList.length > 0"> | |
55 | + <ul> | |
56 | + <li | |
57 | + v-for="(file, idx) in fileList" | |
58 | + :key="idx" | |
59 | + class="pd-10 mt-10" | |
60 | + > | |
61 | + <div v-if="file.name" class="flex justify-between file-wrap"> | |
62 | + <p>{{ file.name }}</p> | |
63 | + <button class="del-btn" @click="handleFileDelete(file, idx)"> | |
64 | + X | |
65 | + </button> | |
66 | + </div> | |
67 | + </li> | |
68 | + </ul> | |
69 | + </div> | |
70 | + </div> | |
71 | + </td> | |
72 | + </tr> | |
73 | + | |
74 | + <!-- 공지글 --> | |
75 | + <tr class="border-top"> | |
76 | + <th class="text-lf"> | |
77 | + 공지글 | |
78 | + </th> | |
79 | + <td colspan="3"> | |
80 | + <div class="d-flex no-gutters"> | |
81 | + <div class="col-md-4"> | |
82 | + <input | |
83 | + type="radio" | |
84 | + name="notice" | |
85 | + id="notice-y" | |
86 | + class="mr5" | |
87 | + value="Y" | |
88 | + v-model="notice.isNotice" | |
89 | + /> | |
90 | + <label for="notice-y">사용</label> | |
91 | + </div> | |
92 | + <div class="col-md-4"> | |
93 | + <input | |
94 | + type="radio" | |
95 | + name="notice" | |
96 | + id="notice-n" | |
97 | + class="mr5" | |
98 | + value="N" | |
99 | + v-model="notice.isNotice" | |
100 | + /> | |
101 | + <label for="notice-n">미사용</label> | |
102 | + </div> | |
103 | + </div> | |
104 | + </td> | |
105 | + </tr> | |
106 | + | |
107 | + <!-- 공지글 게시기간 --> | |
108 | + <tr class="border-top"> | |
109 | + <th class="text-lf"> | |
110 | + 공지글 게시기간 | |
111 | + </th> | |
112 | + <td colspan="3"> | |
113 | + <div class="d-flex no-gutters"> | |
114 | + <div class="col-md-4"> | |
115 | + <input | |
116 | + type="datetime-local" | |
117 | + class="form-control" | |
118 | + v-model="notice.startDate" | |
119 | + @change="checkDateValidity('startDate', $event)" | |
120 | + /> | |
121 | + </div> | |
122 | + <div class="pd-1">-</div> | |
123 | + <div class="col-md-4"> | |
124 | + <input | |
125 | + type="datetime-local" | |
126 | + class="form-control" | |
127 | + v-model="notice.endDate" | |
128 | + @change="checkDateValidity('endDate', $event)" | |
129 | + /> | |
130 | + </div> | |
131 | + </div> | |
132 | + </td> | |
133 | + </tr> | |
134 | + | |
135 | + <!-- 비밀글 --> | |
136 | + <tr class="border-top"> | |
137 | + <th class="text-lf"> | |
138 | + 비밀글 | |
139 | + </th> | |
140 | + <td colspan="3"> | |
141 | + <div class="d-flex no-gutters"> | |
142 | + <div class="col-md-4"> | |
143 | + <input | |
144 | + type="radio" | |
145 | + name="private" | |
146 | + id="private-y" | |
147 | + class="mr5" | |
148 | + value="Y" | |
149 | + v-model="notice.isPrivate" | |
150 | + /> | |
151 | + <label for="private-y">사용</label> | |
152 | + </div> | |
153 | + <div class="col-md-4"> | |
154 | + <input | |
155 | + type="radio" | |
156 | + name="private" | |
157 | + id="private-n" | |
158 | + class="mr5" | |
159 | + value="N" | |
160 | + v-model="notice.isPrivate" | |
161 | + /> | |
162 | + <label for="private-n">미사용</label> | |
163 | + </div> | |
164 | + </div> | |
165 | + </td> | |
166 | + </tr> | |
167 | + </tbody> | |
168 | + </table> | |
169 | + <div class="text-end"> | |
170 | + <button class="btn btn-primary" @click="handleInsert"> | |
171 | + {{ notice.id == null ? "등록" : "수정" }} | |
172 | + </button> | |
173 | + <button class="btn btn-secondary" @click="handleCancel">취소</button> | |
174 | + </div> | |
175 | + </div> | |
176 | + </div> | |
177 | + </section> | |
178 | +</template> | |
179 | + | |
180 | +<script> | |
181 | +import ckeditorComponent from "../../component/ckeditor5/ckeditorComponent.vue"; | |
182 | + | |
183 | + | |
184 | + | |
185 | + | |
186 | +export default { | |
187 | + data() { | |
188 | + return { | |
189 | + notice: { | |
190 | + id: null, | |
191 | + title: "", | |
192 | + isNotice: "Y", | |
193 | + startDate: null, | |
194 | + endDate: null, | |
195 | + isPrivate: "N", | |
196 | + }, | |
197 | + fileList: [], | |
198 | + }; | |
199 | + }, | |
200 | + methods: { | |
201 | + handleCancel() { | |
202 | + if (!confirm("등록을 취소하시겠습니까?")) { | |
203 | + return; | |
204 | + } | |
205 | + this.$router.push({ path: "/list.page" }); | |
206 | + }, | |
207 | + | |
208 | + handleFileInsert() { | |
209 | + this.fileList = [...this.fileList, ...Array.from(this.$refs.file.files)]; | |
210 | + }, | |
211 | + | |
212 | + handleFileDelete(file, index) { | |
213 | + this.fileList.splice(index, 1); | |
214 | + }, | |
215 | + | |
216 | + handleInsert() { | |
217 | + if (!this.validate()) { | |
218 | + return; | |
219 | + } | |
220 | + | |
221 | + if (this.notice.id == null) { | |
222 | + this.saveNotice(); | |
223 | + } else { | |
224 | + this.updateNotice(); | |
225 | + } | |
226 | + }, | |
227 | + | |
228 | + saveNotice() { | |
229 | + alert("공지사항이 등록되었습니다."); | |
230 | + this.$router.push({ path: "/view.page", query: { pageId: this.notice.id } }); | |
231 | + }, | |
232 | + | |
233 | + updateNotice() { | |
234 | + alert("공지사항이 수정되었습니다."); | |
235 | + this.$router.push({ path: "/view.page", query: { pageId: this.notice.id } }); | |
236 | + }, | |
237 | + | |
238 | + validate() { | |
239 | + if (!this.notice.title.trim()) { | |
240 | + alert("제목을 입력해주세요."); | |
241 | + return false; | |
242 | + } | |
243 | + | |
244 | + if (!this.notice.content.trim()) { | |
245 | + alert("내용을 입력해주세요."); | |
246 | + return false; | |
247 | + } | |
248 | + | |
249 | + if (this.notice.isNotice === "Y" && (!this.notice.startDate || !this.notice.endDate)) { | |
250 | + alert("공지기간을 올바르게 설정해주세요."); | |
251 | + return false; | |
252 | + } | |
253 | + | |
254 | + return true; | |
255 | + }, | |
256 | + | |
257 | + checkDateValidity(field, event) { | |
258 | + const val = event.target.value; | |
259 | + if (field === "startDate" && this.notice.endDate && this.notice.endDate < val) { | |
260 | + alert("시작일은 종료일보다 클 수 없습니다."); | |
261 | + this.notice.startDate = null; | |
262 | + } else if (field === "endDate" && this.notice.startDate && this.notice.startDate > val) { | |
263 | + alert("종료일은 시작일보다 작을 수 없습니다."); | |
264 | + this.notice.endDate = null; | |
265 | + } | |
266 | + }, | |
267 | + // 에디터 생성 | |
268 | + createEditor: function () { | |
269 | + // ck에디터 적용 | |
270 | + ClassicEditor | |
271 | + .create(document.querySelector('#editor4'), { | |
272 | + extraPlugins: [this.MyCustomUploadAdapterPlugin], | |
273 | + removePlugins: ['MediaEmbedToolbar'], | |
274 | + image: { | |
275 | + toolbar: ['imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:alignCenter', 'imageStyle:alignRight','|','resizeImage:50','resizeImage:75', 'resizeImage:original','resizeImage:custom',], | |
276 | + resizeOptions: [ | |
277 | + { | |
278 | + name: 'resizeImage:original', | |
279 | + value: null, | |
280 | + icon: 'original' | |
281 | + }, | |
282 | + { | |
283 | + name: 'resizeImage:custom', | |
284 | + value: 'custom', | |
285 | + icon: 'custom' | |
286 | + }, | |
287 | + { | |
288 | + name: 'resizeImage:50', | |
289 | + value: '50', | |
290 | + icon: 'medium' | |
291 | + }, | |
292 | + { | |
293 | + name: 'resizeImage:75', | |
294 | + value: '75', | |
295 | + icon: 'large' | |
296 | + } | |
297 | + ], | |
298 | + }, | |
299 | + | |
300 | + }) | |
301 | + .then(editor => { | |
302 | + this.editor = editor; | |
303 | + editor.setData(this.bbsCn.bbsCn); | |
304 | + editor.model.document.on('change', () => { | |
305 | + this.bbsCn.bbsCn = editor.getData(); | |
306 | + }); | |
307 | + }) | |
308 | + .catch(error => { | |
309 | + console.error('There was a problem initializing the editor.', error); | |
310 | + }); | |
311 | + }, | |
312 | + | |
313 | + beforeDestroy: function() { | |
314 | + if (this.editor) { | |
315 | + this.editor.destroy() | |
316 | + .then(() => { | |
317 | + this.editor = null; | |
318 | + }); | |
319 | + } | |
320 | + }, | |
321 | + | |
322 | + MyCustomUploadAdapterPlugin: function(editor) { | |
323 | + editor.plugins.get('FileRepository').createUploadAdapter = (loader) => { | |
324 | + console.log('loader', loader); | |
325 | + return new UploadAdapter(loader); | |
326 | + } | |
327 | + } | |
328 | + }, | |
329 | + components: { | |
330 | + ckeditorComponent, | |
331 | + }, | |
332 | +}; | |
333 | +</script> | |
334 | + | |
335 | +<style scoped> | |
336 | +td, | |
337 | +th { | |
338 | + padding: 1rem; | |
339 | +} | |
340 | +th { | |
341 | + width: 10rem; | |
342 | +} | |
343 | +#file { | |
344 | + position: absolute; | |
345 | + width: 0; | |
346 | + height: 0; | |
347 | + padding: 0; | |
348 | + overflow: hidden; | |
349 | + border: 0; | |
350 | +} | |
351 | +</style> |
--- client/views/pages/Manager/NoticeList.vue
+++ client/views/pages/Manager/NoticeList.vue
... | ... | @@ -1,20 +1,144 @@ |
1 | 1 |
<template> |
2 |
- <div class="pagetitle"> |
|
3 |
- <h1>공지사항 등록</h1> |
|
2 |
+ <div class="pagetitle"> |
|
3 |
+ <h1>공지사항</h1> |
|
4 | 4 |
</div> |
5 |
+ <!-- End Page Title --> |
|
5 | 6 |
<section class="section"> |
6 |
- <div class="row">ㅇㅇ</div> |
|
7 |
- </section> |
|
8 |
- |
|
9 |
- </template> |
|
10 |
- |
|
11 |
- <script> |
|
12 |
- export default { |
|
13 |
- components: { |
|
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 ChuljangData" :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 --> |
|
83 |
+ </div> |
|
84 |
+ </div> |
|
85 |
+ </div> |
|
86 |
+ </div> |
|
87 |
+ </section> |
|
88 |
+</template> |
|
89 |
+ |
|
90 |
+<script> |
|
91 |
+import { DataTable } from 'simple-datatables' |
|
92 |
+export default { |
|
93 |
+ data() { |
|
94 |
+ return { |
|
95 |
+ // 데이터 초기화 |
|
96 |
+ depts: [2023, 2024, 2025], // 연도 목록 |
|
97 |
+ levels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // 월 목록 |
|
98 |
+ selectedDept: '', |
|
99 |
+ selectedlevel: '', |
|
100 |
+ ChuljangData: [ |
|
101 |
+ { startDate: '', endDate: '', where: '대구', purpose: '', acceptTerms: false }, |
|
102 |
+ { startDate: '', endDate: '', where: '경산', acceptTerms: false }, |
|
103 |
+ // 더 많은 데이터 추가... |
|
104 |
+ ], |
|
105 |
+ filteredData: [], |
|
106 |
+ }; |
|
14 | 107 |
}, |
15 |
- } |
|
16 |
- </script> |
|
108 |
+ computed: { |
|
109 |
+ |
|
110 |
+ }, |
|
111 |
+ methods: { |
|
112 |
+ registerLeave() { |
|
113 |
+ // "NoticeInsert" 페이지로 이동 |
|
114 |
+ this.$router.push({ path: '/NoticeInsert' }); |
|
115 |
+ }, |
|
116 |
+ |
|
117 |
+ deletePending() { |
|
118 |
+ // 선택된 항목만 필터링하여 삭제 |
|
119 |
+ const selectedItems = this.ChuljangData.filter(item => item.acceptTerms); |
|
120 |
+ |
|
121 |
+ // 승인된 항목이 없으면 삭제 진행 |
|
122 |
+ if (selectedItems.length > 0) { |
|
123 |
+ this.ChuljangData = this.ChuljangData.filter(item => !item.acceptTerms); |
|
124 |
+ alert(`${selectedItems.length}개의 항목이 삭제되었습니다.`); |
|
125 |
+ } else { |
|
126 |
+ alert("선택된 항목이 없습니다."); |
|
127 |
+ } |
|
128 |
+ }, |
|
129 |
+ |
|
130 |
+ // 페이지 변경 |
|
131 |
+ changePage(page) { |
|
132 |
+ this.currentPage = page; |
|
133 |
+ }, |
|
134 |
+ }, |
|
135 |
+ mounted() { |
|
136 |
+ |
|
137 |
+ // 처음에는 모든 데이터를 표시 |
|
138 |
+ this.filteredData = this.ChuljangData; |
|
17 | 139 |
|
18 |
- <style scoped> |
|
19 |
- </style> |
|
20 |
-(파일 끝에 줄바꿈 문자 없음) |
|
140 |
+ }, |
|
141 |
+}; |
|
142 |
+</script> |
|
143 |
+ |
|
144 |
+<style scoped></style> |
--- client/views/pages/main/Main.vue
+++ client/views/pages/main/Main.vue
... | ... | @@ -1,8 +1,9 @@ |
1 | 1 |
<template> |
2 |
- <div>Main.vue</div> |
|
2 |
+ <div> <GoogleCalendar/></div> |
|
3 | 3 |
</template> |
4 | 4 |
|
5 | 5 |
<script> |
6 |
+import GoogleCalendar from "../../component/GoogleCalendar.vue" |
|
6 | 7 |
|
7 | 8 |
export default { |
8 | 9 |
data () { |
... | ... | @@ -19,6 +20,7 @@ |
19 | 20 |
|
20 | 21 |
}, |
21 | 22 |
components: { |
23 |
+ GoogleCalendar, |
|
22 | 24 |
}, |
23 | 25 |
mounted() { |
24 | 26 |
console.log('main mounted'); |
--- package-lock.json
+++ package-lock.json
... | ... | @@ -7,21 +7,28 @@ |
7 | 7 |
"dependencies": { |
8 | 8 |
"@babel/cli": "7.19.3", |
9 | 9 |
"@babel/core": "7.19.3", |
10 |
+ "@fullcalendar/core": "^6.1.15", |
|
11 |
+ "@fullcalendar/daygrid": "^6.1.15", |
|
12 |
+ "@fullcalendar/vue3": "^6.1.15", |
|
10 | 13 |
"babel-loader": "8.2.5", |
11 | 14 |
"css-loader": "6.7.1", |
12 | 15 |
"express": "4.18.1", |
13 | 16 |
"file-loader": "6.2.0", |
14 | 17 |
"fs": "0.0.1-security", |
18 |
+ "fullcalendar": "^6.1.15", |
|
19 |
+ "gapi-script": "^1.2.0", |
|
15 | 20 |
"pg": "8.8.0", |
16 | 21 |
"pinia": "^2.2.0", |
17 | 22 |
"realgrid": "^2.8.8", |
18 | 23 |
"simple-datatables": "^9.2.1", |
19 | 24 |
"url-loader": "4.1.1", |
20 | 25 |
"vue": "^3.5.13", |
26 |
+ "vue-cookies": "^1.8.6", |
|
21 | 27 |
"vue-loader": "^17.0.0", |
22 | 28 |
"vue-router": "4.1.5", |
23 | 29 |
"vue-style-loader": "4.1.3", |
24 | 30 |
"vue3-sfc-loader": "^0.8.4", |
31 |
+ "vuex": "^4.1.0", |
|
25 | 32 |
"webpack": "5.74.0", |
26 | 33 |
"webpack-cli": "4.10.0" |
27 | 34 |
} |
... | ... | @@ -365,6 +372,76 @@ |
365 | 372 |
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", |
366 | 373 |
"engines": { |
367 | 374 |
"node": ">=10.0.0" |
375 |
+ } |
|
376 |
+ }, |
|
377 |
+ "node_modules/@fullcalendar/core": { |
|
378 |
+ "version": "6.1.15", |
|
379 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", |
|
380 |
+ "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", |
|
381 |
+ "license": "MIT", |
|
382 |
+ "dependencies": { |
|
383 |
+ "preact": "~10.12.1" |
|
384 |
+ } |
|
385 |
+ }, |
|
386 |
+ "node_modules/@fullcalendar/daygrid": { |
|
387 |
+ "version": "6.1.15", |
|
388 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", |
|
389 |
+ "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", |
|
390 |
+ "license": "MIT", |
|
391 |
+ "peerDependencies": { |
|
392 |
+ "@fullcalendar/core": "~6.1.15" |
|
393 |
+ } |
|
394 |
+ }, |
|
395 |
+ "node_modules/@fullcalendar/interaction": { |
|
396 |
+ "version": "6.1.15", |
|
397 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", |
|
398 |
+ "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", |
|
399 |
+ "license": "MIT", |
|
400 |
+ "peerDependencies": { |
|
401 |
+ "@fullcalendar/core": "~6.1.15" |
|
402 |
+ } |
|
403 |
+ }, |
|
404 |
+ "node_modules/@fullcalendar/list": { |
|
405 |
+ "version": "6.1.15", |
|
406 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz", |
|
407 |
+ "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==", |
|
408 |
+ "license": "MIT", |
|
409 |
+ "peerDependencies": { |
|
410 |
+ "@fullcalendar/core": "~6.1.15" |
|
411 |
+ } |
|
412 |
+ }, |
|
413 |
+ "node_modules/@fullcalendar/multimonth": { |
|
414 |
+ "version": "6.1.15", |
|
415 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz", |
|
416 |
+ "integrity": "sha512-sEZY6jbOYkeF9TwhUldG+UUVv+hiPlGkS8zZEgPR7ypcjhipyA03c5rPjx7N6huOHqh6lCMH59zlohLooQRlaw==", |
|
417 |
+ "license": "MIT", |
|
418 |
+ "dependencies": { |
|
419 |
+ "@fullcalendar/daygrid": "~6.1.15" |
|
420 |
+ }, |
|
421 |
+ "peerDependencies": { |
|
422 |
+ "@fullcalendar/core": "~6.1.15" |
|
423 |
+ } |
|
424 |
+ }, |
|
425 |
+ "node_modules/@fullcalendar/timegrid": { |
|
426 |
+ "version": "6.1.15", |
|
427 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", |
|
428 |
+ "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", |
|
429 |
+ "license": "MIT", |
|
430 |
+ "dependencies": { |
|
431 |
+ "@fullcalendar/daygrid": "~6.1.15" |
|
432 |
+ }, |
|
433 |
+ "peerDependencies": { |
|
434 |
+ "@fullcalendar/core": "~6.1.15" |
|
435 |
+ } |
|
436 |
+ }, |
|
437 |
+ "node_modules/@fullcalendar/vue3": { |
|
438 |
+ "version": "6.1.15", |
|
439 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.15.tgz", |
|
440 |
+ "integrity": "sha512-ctfTICGrNEIj7gmLHQcUYe0WzDTSW5Vd9hyOnVChxPU75AZU9WqdDMkHwJYnfNxNhT6QQuiMHq/qsRRd5zQwOw==", |
|
441 |
+ "license": "MIT", |
|
442 |
+ "peerDependencies": { |
|
443 |
+ "@fullcalendar/core": "~6.1.15", |
|
444 |
+ "vue": "^3.0.11" |
|
368 | 445 |
} |
369 | 446 |
}, |
370 | 447 |
"node_modules/@jridgewell/gen-mapping": { |
... | ... | @@ -1655,10 +1732,30 @@ |
1655 | 1732 |
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" |
1656 | 1733 |
} |
1657 | 1734 |
}, |
1735 |
+ "node_modules/fullcalendar": { |
|
1736 |
+ "version": "6.1.15", |
|
1737 |
+ "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz", |
|
1738 |
+ "integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==", |
|
1739 |
+ "license": "MIT", |
|
1740 |
+ "dependencies": { |
|
1741 |
+ "@fullcalendar/core": "~6.1.15", |
|
1742 |
+ "@fullcalendar/daygrid": "~6.1.15", |
|
1743 |
+ "@fullcalendar/interaction": "~6.1.15", |
|
1744 |
+ "@fullcalendar/list": "~6.1.15", |
|
1745 |
+ "@fullcalendar/multimonth": "~6.1.15", |
|
1746 |
+ "@fullcalendar/timegrid": "~6.1.15" |
|
1747 |
+ } |
|
1748 |
+ }, |
|
1658 | 1749 |
"node_modules/function-bind": { |
1659 | 1750 |
"version": "1.1.1", |
1660 | 1751 |
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", |
1661 | 1752 |
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" |
1753 |
+ }, |
|
1754 |
+ "node_modules/gapi-script": { |
|
1755 |
+ "version": "1.2.0", |
|
1756 |
+ "resolved": "https://registry.npmjs.org/gapi-script/-/gapi-script-1.2.0.tgz", |
|
1757 |
+ "integrity": "sha512-NKTVKiIwFdkO1j1EzcrWu/Pz7gsl1GmBmgh+qhuV2Ytls04W/Eg5aiBL91SCiBM9lU0PMu7p1hTVxhh1rPT5Lw==", |
|
1758 |
+ "license": "MIT" |
|
1662 | 1759 |
}, |
1663 | 1760 |
"node_modules/gensync": { |
1664 | 1761 |
"version": "1.0.0-beta.2", |
... | ... | @@ -2593,6 +2690,16 @@ |
2593 | 2690 |
"node": ">=0.10.0" |
2594 | 2691 |
} |
2595 | 2692 |
}, |
2693 |
+ "node_modules/preact": { |
|
2694 |
+ "version": "10.12.1", |
|
2695 |
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", |
|
2696 |
+ "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", |
|
2697 |
+ "license": "MIT", |
|
2698 |
+ "funding": { |
|
2699 |
+ "type": "opencollective", |
|
2700 |
+ "url": "https://opencollective.com/preact" |
|
2701 |
+ } |
|
2702 |
+ }, |
|
2596 | 2703 |
"node_modules/proxy-addr": { |
2597 | 2704 |
"version": "2.0.7", |
2598 | 2705 |
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", |
... | ... | @@ -3165,6 +3272,12 @@ |
3165 | 3272 |
} |
3166 | 3273 |
} |
3167 | 3274 |
}, |
3275 |
+ "node_modules/vue-cookies": { |
|
3276 |
+ "version": "1.8.6", |
|
3277 |
+ "resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.8.6.tgz", |
|
3278 |
+ "integrity": "sha512-e2kYaHj1Y/zVsBSM3KWlOoVJ5o3l4QZjytNU7xdCgmkw3521CMUerqHekBGZKXXC1oRxYljBeeOK2SCel6cKuw==", |
|
3279 |
+ "license": "MIT" |
|
3280 |
+ }, |
|
3168 | 3281 |
"node_modules/vue-loader": { |
3169 | 3282 |
"version": "17.0.0", |
3170 | 3283 |
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz", |
... | ... | @@ -3290,6 +3403,18 @@ |
3290 | 3403 |
"version": "0.8.4", |
3291 | 3404 |
"resolved": "https://registry.npmjs.org/vue3-sfc-loader/-/vue3-sfc-loader-0.8.4.tgz", |
3292 | 3405 |
"integrity": "sha512-eziaIrk/N9f9OCpyFEkR6vMsZUHcF5mQslXjffwcb5Iq6EuU74QrlpBeJqA04MvAGT7f5O8la2v9k3NtQnJb3Q==" |
3406 |
+ }, |
|
3407 |
+ "node_modules/vuex": { |
|
3408 |
+ "version": "4.1.0", |
|
3409 |
+ "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", |
|
3410 |
+ "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", |
|
3411 |
+ "license": "MIT", |
|
3412 |
+ "dependencies": { |
|
3413 |
+ "@vue/devtools-api": "^6.0.0-beta.11" |
|
3414 |
+ }, |
|
3415 |
+ "peerDependencies": { |
|
3416 |
+ "vue": "^3.2.0" |
|
3417 |
+ } |
|
3293 | 3418 |
}, |
3294 | 3419 |
"node_modules/watchpack": { |
3295 | 3420 |
"version": "2.4.0", |
... | ... | @@ -3705,6 +3830,54 @@ |
3705 | 3830 |
"version": "0.5.7", |
3706 | 3831 |
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", |
3707 | 3832 |
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==" |
3833 |
+ }, |
|
3834 |
+ "@fullcalendar/core": { |
|
3835 |
+ "version": "6.1.15", |
|
3836 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz", |
|
3837 |
+ "integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==", |
|
3838 |
+ "requires": { |
|
3839 |
+ "preact": "~10.12.1" |
|
3840 |
+ } |
|
3841 |
+ }, |
|
3842 |
+ "@fullcalendar/daygrid": { |
|
3843 |
+ "version": "6.1.15", |
|
3844 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz", |
|
3845 |
+ "integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==", |
|
3846 |
+ "requires": {} |
|
3847 |
+ }, |
|
3848 |
+ "@fullcalendar/interaction": { |
|
3849 |
+ "version": "6.1.15", |
|
3850 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", |
|
3851 |
+ "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", |
|
3852 |
+ "requires": {} |
|
3853 |
+ }, |
|
3854 |
+ "@fullcalendar/list": { |
|
3855 |
+ "version": "6.1.15", |
|
3856 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz", |
|
3857 |
+ "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==", |
|
3858 |
+ "requires": {} |
|
3859 |
+ }, |
|
3860 |
+ "@fullcalendar/multimonth": { |
|
3861 |
+ "version": "6.1.15", |
|
3862 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz", |
|
3863 |
+ "integrity": "sha512-sEZY6jbOYkeF9TwhUldG+UUVv+hiPlGkS8zZEgPR7ypcjhipyA03c5rPjx7N6huOHqh6lCMH59zlohLooQRlaw==", |
|
3864 |
+ "requires": { |
|
3865 |
+ "@fullcalendar/daygrid": "~6.1.15" |
|
3866 |
+ } |
|
3867 |
+ }, |
|
3868 |
+ "@fullcalendar/timegrid": { |
|
3869 |
+ "version": "6.1.15", |
|
3870 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", |
|
3871 |
+ "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", |
|
3872 |
+ "requires": { |
|
3873 |
+ "@fullcalendar/daygrid": "~6.1.15" |
|
3874 |
+ } |
|
3875 |
+ }, |
|
3876 |
+ "@fullcalendar/vue3": { |
|
3877 |
+ "version": "6.1.15", |
|
3878 |
+ "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.15.tgz", |
|
3879 |
+ "integrity": "sha512-ctfTICGrNEIj7gmLHQcUYe0WzDTSW5Vd9hyOnVChxPU75AZU9WqdDMkHwJYnfNxNhT6QQuiMHq/qsRRd5zQwOw==", |
|
3880 |
+ "requires": {} |
|
3708 | 3881 |
}, |
3709 | 3882 |
"@jridgewell/gen-mapping": { |
3710 | 3883 |
"version": "0.3.2", |
... | ... | @@ -4709,10 +4882,28 @@ |
4709 | 4882 |
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", |
4710 | 4883 |
"optional": true |
4711 | 4884 |
}, |
4885 |
+ "fullcalendar": { |
|
4886 |
+ "version": "6.1.15", |
|
4887 |
+ "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz", |
|
4888 |
+ "integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==", |
|
4889 |
+ "requires": { |
|
4890 |
+ "@fullcalendar/core": "~6.1.15", |
|
4891 |
+ "@fullcalendar/daygrid": "~6.1.15", |
|
4892 |
+ "@fullcalendar/interaction": "~6.1.15", |
|
4893 |
+ "@fullcalendar/list": "~6.1.15", |
|
4894 |
+ "@fullcalendar/multimonth": "~6.1.15", |
|
4895 |
+ "@fullcalendar/timegrid": "~6.1.15" |
|
4896 |
+ } |
|
4897 |
+ }, |
|
4712 | 4898 |
"function-bind": { |
4713 | 4899 |
"version": "1.1.1", |
4714 | 4900 |
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", |
4715 | 4901 |
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" |
4902 |
+ }, |
|
4903 |
+ "gapi-script": { |
|
4904 |
+ "version": "1.2.0", |
|
4905 |
+ "resolved": "https://registry.npmjs.org/gapi-script/-/gapi-script-1.2.0.tgz", |
|
4906 |
+ "integrity": "sha512-NKTVKiIwFdkO1j1EzcrWu/Pz7gsl1GmBmgh+qhuV2Ytls04W/Eg5aiBL91SCiBM9lU0PMu7p1hTVxhh1rPT5Lw==" |
|
4716 | 4907 |
}, |
4717 | 4908 |
"gensync": { |
4718 | 4909 |
"version": "1.0.0-beta.2", |
... | ... | @@ -5340,6 +5531,11 @@ |
5340 | 5531 |
"xtend": "^4.0.0" |
5341 | 5532 |
} |
5342 | 5533 |
}, |
5534 |
+ "preact": { |
|
5535 |
+ "version": "10.12.1", |
|
5536 |
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", |
|
5537 |
+ "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==" |
|
5538 |
+ }, |
|
5343 | 5539 |
"proxy-addr": { |
5344 | 5540 |
"version": "2.0.7", |
5345 | 5541 |
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", |
... | ... | @@ -5719,6 +5915,11 @@ |
5719 | 5915 |
"@vue/shared": "3.5.13" |
5720 | 5916 |
} |
5721 | 5917 |
}, |
5918 |
+ "vue-cookies": { |
|
5919 |
+ "version": "1.8.6", |
|
5920 |
+ "resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.8.6.tgz", |
|
5921 |
+ "integrity": "sha512-e2kYaHj1Y/zVsBSM3KWlOoVJ5o3l4QZjytNU7xdCgmkw3521CMUerqHekBGZKXXC1oRxYljBeeOK2SCel6cKuw==" |
|
5922 |
+ }, |
|
5722 | 5923 |
"vue-loader": { |
5723 | 5924 |
"version": "17.0.0", |
5724 | 5925 |
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz", |
... | ... | @@ -5816,6 +6017,14 @@ |
5816 | 6017 |
"resolved": "https://registry.npmjs.org/vue3-sfc-loader/-/vue3-sfc-loader-0.8.4.tgz", |
5817 | 6018 |
"integrity": "sha512-eziaIrk/N9f9OCpyFEkR6vMsZUHcF5mQslXjffwcb5Iq6EuU74QrlpBeJqA04MvAGT7f5O8la2v9k3NtQnJb3Q==" |
5818 | 6019 |
}, |
6020 |
+ "vuex": { |
|
6021 |
+ "version": "4.1.0", |
|
6022 |
+ "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", |
|
6023 |
+ "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", |
|
6024 |
+ "requires": { |
|
6025 |
+ "@vue/devtools-api": "^6.0.0-beta.11" |
|
6026 |
+ } |
|
6027 |
+ }, |
|
5819 | 6028 |
"watchpack": { |
5820 | 6029 |
"version": "2.4.0", |
5821 | 6030 |
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", |
--- package.json
+++ package.json
... | ... | @@ -2,21 +2,28 @@ |
2 | 2 |
"dependencies": { |
3 | 3 |
"@babel/cli": "7.19.3", |
4 | 4 |
"@babel/core": "7.19.3", |
5 |
+ "@fullcalendar/core": "^6.1.15", |
|
6 |
+ "@fullcalendar/daygrid": "^6.1.15", |
|
7 |
+ "@fullcalendar/vue3": "^6.1.15", |
|
5 | 8 |
"babel-loader": "8.2.5", |
6 | 9 |
"css-loader": "6.7.1", |
7 | 10 |
"express": "4.18.1", |
8 | 11 |
"file-loader": "6.2.0", |
9 | 12 |
"fs": "0.0.1-security", |
13 |
+ "fullcalendar": "^6.1.15", |
|
14 |
+ "gapi-script": "^1.2.0", |
|
10 | 15 |
"pg": "8.8.0", |
11 | 16 |
"pinia": "^2.2.0", |
12 | 17 |
"realgrid": "^2.8.8", |
13 | 18 |
"simple-datatables": "^9.2.1", |
14 | 19 |
"url-loader": "4.1.1", |
15 | 20 |
"vue": "^3.5.13", |
21 |
+ "vue-cookies": "^1.8.6", |
|
16 | 22 |
"vue-loader": "^17.0.0", |
17 | 23 |
"vue-router": "4.1.5", |
18 | 24 |
"vue-style-loader": "4.1.3", |
19 | 25 |
"vue3-sfc-loader": "^0.8.4", |
26 |
+ "vuex": "^4.1.0", |
|
20 | 27 |
"webpack": "5.74.0", |
21 | 28 |
"webpack-cli": "4.10.0" |
22 | 29 |
}, |
--- webpack.config.js
+++ webpack.config.js
... | ... | @@ -19,10 +19,12 @@ |
19 | 19 |
}, { |
20 | 20 |
test: /\.(js|jsx)?$/, |
21 | 21 |
loader: 'babel-loader', |
22 |
- }, { |
|
22 |
+ }, |
|
23 |
+ { |
|
23 | 24 |
test: /\.css$/, |
24 | 25 |
use: ['vue-style-loader', 'css-loader'] |
25 |
- }, { |
|
26 |
+ }, |
|
27 |
+ { |
|
26 | 28 |
test: /\.(jpe?g|png|gif|svg|ttf|eot|woff|woff2)$/i, |
27 | 29 |
use: [{ |
28 | 30 |
loader:'url-loader', |
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?