jichoi / lms_front star
권지수 2024-08-13
Merge branch 'master' of http://210.180.118.83/jichoi/lms_front
@bcd21ce0100ac5db86305272d57ec0180a9e7bbf
client/resources/css/common.css
--- client/resources/css/common.css
+++ client/resources/css/common.css
@@ -934,6 +934,10 @@
     color: #999999;
 }
 
+.black {
+    color: #000000;
+}
+
 .cursor {
     cursor: pointer;
 }
client/resources/css/style.css
--- client/resources/css/style.css
+++ client/resources/css/style.css
@@ -219,7 +219,7 @@
 }
 
 .menu a:first-child button.active {
-  background-image: url('../../resources/img/btn05_1_s.png');
+  background-image: url('../../resources/img/btn04_1_s.png');
   width: 252px;
   /* 마이페이지 활성화 이미지 */
 }
@@ -231,7 +231,7 @@
 }
 
 .menu a:last-child button.active {
-  background-image: url('../../resources/img/btn04_1_s.png');
+  background-image: url('../../resources/img/btn05_1_s.png');
   width: 259px;
   /* 학습일정계획 활성화 이미지 */
 }
@@ -284,7 +284,7 @@
 .mypage .photobook .title1{font-family: 'ONEMobilePOPOTF'; font-weight: 100;}
 .mypage .textbook .text {border-radius:  12px 12px 0px 0px; height: 68px;
 }
-.mypage .textbook .box{height: 199px; text-align: center; padding: 10px;}
+.mypage .textbook .box{height: fit-content; text-align: center; padding: 10px;}
 .mypage .book-red .text{background-color: #DB2B39;}
 /* .mypage .textbook:nth-child(2) .text */
 .mypage .book-blue .text{background-color:#2D7DD2;}
@@ -674,7 +674,7 @@
 .listenGroup img.bg {
   position: absolute;
   top: 0;
-  width: 1670px;
+  width: 100%;
   height: 710px;
 }
 
@@ -690,14 +690,23 @@
   top: 45%;
     left: 31%;
   transform: translate(-50%, -50%);
-  width: 900px;
+  /* width: 900px; */
+}
+.dropGroup .textbox {
+  position: absolute;
+  top: 45%;
+    left: 53%;
+  transform: translate(-50%, -50%);
+  /* width: 900px; */
 }
 
 .listenGroup .textbox p {
   font-size: 28px;
   line-height: 45px;
+  width: 100%;
+  
 }
-
+p.textbox {width: 100%;}
 .listenGroup p.title {
   font-size: 42px;
   text-shadow: 0px 0px 0 #fff, 0px 0px 0 #fff, 0px 0px 0 #fff, 0px 0px 0 #fff;
@@ -744,7 +753,7 @@
 
 /* step2_2 */
 .popTxt{position: relative; }
-.popTxt img:last-child{position: absolute; top: 0; left: 0;}
+
 
 h4{font-size: 20px;}
 
 
client/resources/img/jumpingRabbit.gif (Binary) (added)
+++ client/resources/img/jumpingRabbit.gif
Binary file is not shown
 
client/views/component/TextToImage.vue (added)
+++ client/views/component/TextToImage.vue
@@ -0,0 +1,244 @@
+<template>
+    <div class="flex justify-center align-center" style="margin-top : 30px">
+        <div class="content title-box aibox">
+            <p class="title mt25 title-bg">모르는 단어를 물어봐~!</p>
+            <div class="propmt-container">
+                <div class="input-conatiner">
+                    <input v-model="word" @keyup.enter="startProgress(word)"/>
+                    <button @click="startProgress(word)"> <p> 생성하기 </p> </button>
+                </div>
+                <div class="progress-container">
+                    <div class="progress-bar" :style="{ width: progressWidth + '%' }">{{ Math.floor(progressWidth) }}%</div>
+                    
+                    <!-- 로딩 이미지 -->
+                    <div v-if="progressWidth < 100 && progressWidth > 1" class="loading-container">
+                        <img src="../../resources/img/jumpingRabbit.gif" alt="Loading" class="loading-gif"/>
+                        <p> 잠깐만 기다려봐~ </p>
+                    </div>
+
+                </div>
+                <div v-if="visibleWord" class="result-container">
+                    <img :src="imageSrc"/>
+                    <div class="word-container">
+                        <h2> {{ inputWord }}  </h2>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    
+</template>
+
+<script>
+import axios from 'axios';
+
+export default {
+    data() {
+        return {
+            progressWidth: 0,
+            inputWord : "",
+
+            // 하드 코딩 -> 수정 필요 : API
+            word : "",
+            imageSrc : "",
+            imageData: null, // 서버에서 받아온 이미지 데이터 저장
+
+            visibleWord : false,
+        };
+    },
+    mounted() {
+        
+    },
+    methods: {
+        // 상태 진행 바(progress bar)
+        startProgress(word) {
+            if (this.progressWidth == 100) {
+                this.resetProgress();
+            }
+
+            if (this.progressWidth > 0) return; // 이미 진행 중이면 중복 실행 방지
+
+            this.setWord(word);
+
+            const interval = 300; // 30ms
+            const totalDuration = 3000; // 총 시간 : 10초
+            const steps = totalDuration / interval;
+            const increment = 100 / steps;
+
+            const progressInterval = setInterval(() => {
+                this.progressWidth += increment;
+                if (this.progressWidth >= 100) {
+                    this.progressWidth = 100;
+                    clearInterval(progressInterval);
+                    this.visibleWord = true;
+
+                     // 진행이 완료된 후에 이미지 데이터 렌더링
+                    if (this.imageData) {
+                        this.imageSrc = this.imageUrl;
+                    }
+                }
+            }, interval);
+
+            this.getAiImage();
+        },
+        setWord(word) {
+            if (this.progressWidth > 0 && this.progressWidth < 100) return; // progressWidth가 0 또는 100이 아니면 실행 중지
+            this.inputWord = word;
+            this.visibleWord = false;
+        },
+        
+        resetProgress() {
+            this.progressWidth = 0;
+            this.visibleWord = false;
+            this.imageSrc = ""; // 이미지 URL 초기화
+            this.imageData = null; // 이미지 데이터 초기화
+        },
+
+        // 이미지 API
+        async getAiImage(){
+            const url = "http://takensoftai.iptime.org:20200/generate_json";
+            // console.log(`word : ${this.word}`);
+            try {
+                const response = await axios({
+                    url: url,
+                    method: "post",
+                    headers: {
+                        "Content-Type": "application/json; charset=UTF-8",
+                    },
+                    responseType: 'arraybuffer',
+                    data: {
+                        text: this.word
+                    },
+                });
+                
+                // console.log(`응답 : ${response}`);
+
+                // 바이너리 데이터 -> Blob으로 변환
+                const blob = new Blob([response.data], { type: 'image/png' });
+
+                // Blob에서 객체 URL 생성
+                const imageUrl = URL.createObjectURL(blob);
+
+                // 이미지 URL 설정
+                this.imageSrc = imageUrl;
+            } catch (err) {
+                console.log(err);
+            }
+        }
+    }
+}
+</script>
+
+<style>
+.propmt-container{
+    padding: 0px 50px 30px 50px;
+}
+/* 입력 컨테이너  */
+.input-conatiner{
+    display: flex;
+    align-items: center;
+    gap: 30px;
+    height: 50px;
+}
+.input-conatiner input{
+    width: 90%;
+    height: 100%;
+    padding: 10px 30px;
+    border: none;
+    border-radius: 10px;
+    box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
+    outline: none;
+    font-size : 28px;
+}
+.input-conatiner button{
+    width: 15%;
+    height: 100%;
+    border: none;
+    border-radius: 10px;
+    cursor: pointer;
+    background-color: #ffba08;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.input-conatiner button p{
+    font-size : 28px;
+}
+
+/* 진행 상태바 */
+.progress-container {
+    width: 100%;
+    background-color: #ffffff;
+    border-radius: 5px;
+    overflow: hidden;
+    margin-top: 20px;
+    display : flex;
+    flex-direction: column;
+    gap :30px;
+}
+
+.progress-bar {
+    height: 30px;
+    width: 0;
+    background-color: #4caf50;
+    text-align: center;
+    line-height: 30px;
+    color: white;
+    border : none;
+    border-radius: 5px;
+    transition: width 0.3s;
+    font-size : 20px;
+}
+
+/* 로딩 gif */
+.loading-container{
+    display : flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 30px;
+    text-align: center;
+    margin-top : 30px;
+    padding-bottom : 40px;
+}
+.loading-gif{
+    width: 25%;
+    border-radius: 500px;
+}
+.loading-container p {
+    font-size : 25px;
+}
+
+/* 결과 container */
+.result-container{
+    margin-top: 30px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 30px;
+}
+.result-container img{
+    width : 30%;
+}
+.word-container{
+    width: 30%;
+    text-align: center;
+    padding: 20px 0px;
+    border: 3px solid #4caf50;
+    border-radius: 8px;
+    display: flex;
+    flex-direction: column;
+    gap: 15px;
+}
+.word-container h2 {
+    font-size : 28px;
+}
+
+.aibox{
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    height: auto;
+    padding: 30px 0px;
+}
+</style>
client/views/layout/Menu.vue
--- client/views/layout/Menu.vue
+++ client/views/layout/Menu.vue
@@ -1,20 +1,20 @@
 <template>
     <div class="menu flex" style="gap: 30px;">
-        <router-link to="/MyPage.page">
-            <button :class="{ active: activeButton === 'mypage' }" type="submit" @click="goToApp('mypage')">
-                <p :class="{ active: activeButton === 'mypage' }">마이페이지</p>
-            </button>
+        <router-link to="/MyPlan.page">
+          <button :class="{ active: activeButton === 'schedule' }" type="submit" @click="goToApp('schedule')">
+            <p :class="{ active: activeButton === 'schedule' }">학습일정계획</p>
+          </button>
         </router-link>
         <router-link to="/Dashboard.page">
           <button :class="{ active: activeButton === 'dashboard' }" type="submit" @click="goToApp('dashboard')">
             <p :class="{ active: activeButton === 'dashboard' }">Dashboard</p>
           </button>
         </router-link>
-      <router-link to="/MyPlan.page">
-        <button :class="{ active: activeButton === 'schedule' }" type="submit" @click="goToApp('schedule')">
-          <p :class="{ active: activeButton === 'schedule' }">학습일정계획</p>
-        </button>
-      </router-link>
+        <router-link to="/MyPage.page">
+            <button :class="{ active: activeButton === 'mypage' }" type="submit" @click="goToApp('mypage')">
+                <p :class="{ active: activeButton === 'mypage' }">마이페이지</p>
+            </button>
+        </router-link>
     </div>
   </template>
   
client/views/layout/Side.vue
--- client/views/layout/Side.vue
+++ client/views/layout/Side.vue
@@ -1,6 +1,8 @@
 <template>
     <div class="side">
-        <div class="logo mb25"><img src="../../resources/img/logo2.png" alt=""></div>
+        <router-link to="/">
+            <div class="logo mb25"><img src="../../resources/img/logo2.png" alt=""></div>
+        </router-link>
         <div class="profile mb30">
             <div class="flex align-start">
                 <img src="../../resources/img/img16_s.png" alt="">
@@ -14,7 +16,7 @@
             </div>
             <hr>
             <p class="title2 mb25">최근 학습 히스토리</p>
-            <ul class="flex justify-between ml30">
+            <ul class="flex justify-between ml30 mb30">
                 <li>자학사 3학년 2학기</li>
                 <li>자학사 3학년 2학기</li>
             </ul>
@@ -25,14 +27,14 @@
             <div class="memo mb15"><textarea name="" id="" placeholder="궁금한 것을 적어보세요."></textarea></div>
             <div class="flex justify-end"><button>질문하기</button></div>
         </div>
-        <div class="btn-wrap flex justify-between">
+        <!-- <div class="btn-wrap flex justify-between">
             <button class="login-btn" @click="handleClick" >
                 <img  src="../../resources/img/btn07_s.png" alt="">
                 <p>{{ buttonText }}</p>
             </button>
             
             <button  class="login-btn" type="submit" ><img src="../../resources/img/btn07_s.png" alt=""> <p >오늘 할 다른 공부</p></button>
-        </div>
+        </div> -->
     </div>
 </template>
  
client/views/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -8,6 +8,7 @@
 import MyPlan2 from './main/MyPlan2.vue';
 import PhotoBook from './main/PhotoBook.vue';
 import Dashboard from './main/Dashboard.vue';
+import AIDashboard from './main/AIDashboard.vue';
 import Camera from "./main/Camera.vue"
 import PhotoDesign from "./main/PhotoDesign.vue"
 import PhotoEdit from "./main/PhotoEdit.vue"
@@ -85,11 +86,7 @@
 import TextDetail from "./teacher/TextDetail.vue";
 import QuestionList from "./teacher/QuestionList.vue";
 import QuestionInsert from "./teacher/QuestionInsert.vue";
-<<<<<<< HEAD
-import QuestionDetail from "./teacher/QuestionDetail.vue"; 
-=======
 import QuestionDetail from "./teacher/QuestionDetail.vue";
->>>>>>> e47769b90c7ad4f0b34f38bb2a56a8a69a894941
 import VocaList from "./teacher/VocaList.vue";
 import VocaInsert from "./teacher/VocaInsert.vue";
 import VocaDetail from "./teacher/VocaDetail.vue";
@@ -114,6 +111,7 @@
         component: Main,
         children: [
             { path: '/Dashboard.page', name: 'Dashboard', component: Dashboard },
+            { path: '/AIDashboard.page', name: 'AIDashboard', component: AIDashboard },
             { path: '/MyPage.page', name: 'MyPage', component: MyPage },
             { path: '/MyPlan.page', name: 'MyPlan', component: MyPlan },
             { path: '/MyPlan2.page', name: 'MyPlan2', component: MyPlan2 },
@@ -198,11 +196,7 @@
             { path: '/TextDetail.page', name: 'TextDetail', component: TextDetail },
             { path: '/QuestionList.page', name: 'QuestionList', component: QuestionList },
             { path: '/QuestionInsert.page', name: 'QuestionInsert', component: QuestionInsert },
-<<<<<<< HEAD
-            { path: '/QuestionDetail.page', name: 'QuestionDetail', component: QuestionDetail }, 
-=======
             { path: '/QuestionDetail.page', name: 'QuestionDetail', component: QuestionDetail },
->>>>>>> e47769b90c7ad4f0b34f38bb2a56a8a69a894941
             { path: '/VocaList.page', name: 'VocaList', component: VocaList },
             { path: '/VocaInsert.page', name: 'VocaInsert', component: VocaInsert },
             { path: '/VocaDetail.page', name: 'VocaDetail', component: VocaDetail },
 
client/views/pages/main/AIDashboard.vue (added)
+++ client/views/pages/main/AIDashboard.vue
@@ -0,0 +1,853 @@
+<template>
+    <div class="main">
+        <div class="race-wrap">
+            <div class="title-box">
+                <p class="title">AI 학습 보드</p>
+                <p class="subtitle">hi my name is dd!</p>
+            </div>
+            <div class="race-box">
+                <div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div>
+                <div class="rcon flex justify-end mb5">
+                    <div class="race-btn" @click="goToPage('Chapter1')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="1">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>지문1</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter2')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="2">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>단어장</p>
+                    </div>
+                </div>
+                <div class="lcon flex justify-between mb5">
+                    <div class="race-btn" @click="goToPage('Chapter7')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="7">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>문제1</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter6')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="6">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>단어장</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter5')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="5">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>지문2</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter4')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="4">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>문제2</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter3')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="3">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>문제1</p>
+                    </div>
+                </div>
+                <div class="rcon flex">
+                    <div class="race-btn" @click="goToPage('Chapter8')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="8">
+                            <img :src="item.imgSrc3">
+                            <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p class="long">중간 평가</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter9')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="9">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>지문3</p>
+                    </div>
+                    <div class="race-btn" @click="goToPage('Chapter10')">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                            data-num="10">
+                            <img :src="item.imgSrc1">
+                            <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p>단어장</p>
+                    </div>
+
+                    <div class="race-btn">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                        data-num="11">
+                            <img :src="item.imgSrc3">
+                            <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p class="long">최종 평가</p>
+                    </div>
+                </div>
+                <div class="rabbit-end" @click="ShowPopup"><img src="../../../resources/img/img138_72s.png" alt=""></div>
+            </div>
+
+            <!-- 팝업 -->
+            <div v-show="searchOpen2" class="popup-wrap">
+                <div class="popup-box">
+                    <button type="button" class="popup-close-btn" style="position:absolute; top:10px; right: 10px;"
+                        @click="closeModal">
+                        <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                    </button>
+
+                    <div class="mb30 text-ct">
+                        <p class="title1 mb20">1단원이 끝났습니다!</p>
+                        <p class="title1"><em class="yellow">기념사진</em>을 촬영하러 가요</p>
+                    </div>
+                    <div class="flex justify-center">
+                        <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal">
+                            사진 촬영
+                        </button>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 카메라 모달 -->
+            <article v-show="showCameraModal" class="popup-wrap">
+                <div class="popup-box" style="top: 500px; left:500px">
+                    <div class="flex mb10 justify-between">
+                        <p class="popup-title">사진 촬영</p>
+                        <button type="button" class="popup-close-btn" @click="closeModal">
+                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                        </button>
+                    </div>
+                    <div class="box">
+                        <div style="width: 100%;">
+                            <!-- 여기에 카메라 기능을 구현 -->
+                            <!-- <p>카메라 모듈이 여기에 위치합니다.</p> -->
+
+                            <div id="container" ref="container">
+                                <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored"
+                                    @canplay="onVideoLoaded"></video>
+                            </div>
+
+                        </div>
+                    </div>
+                    <div class="flex justify-center mt20">
+                        <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto"
+                            :disabled="!videoReady">
+                            사진 촬영
+                        </button>
+
+                    </div>
+                </div>
+            </article>
+
+            <!-- 사진 모달 -->
+            <article v-show="showPhotoModal" class="popup-wrap">
+                <div class="popup-box" style="top: 500px; left: auto">
+                    <div class="flex mb10 justify-between">
+                        <p class="popup-title">사진 꾸미기</p>
+                        <button type="button" class="popup-close-btn" @click="closePhotoModal">
+                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                        </button>
+                    </div>
+                    <div class="flex justify-between align-center" style="gap: 40px;">
+                        <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
+                            <div class="tool">
+                                <div class="flex justify-center mb20" style="gap: 20px;">
+                                    <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo"
+                                        :key="index" @click="updateContent(index)"
+                                        :class="{ active: selectedIndex === index }">
+                                        <img :src="item.imgSrc1" style="display: block;">
+                                        <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;">
+                                    </button>
+                                </div>
+                            </div>
+
+                            <div class="stickers" v-show="!stickersVisible">
+                                <div class="toolbar">
+                                    <label for="brushSize" style="font-size: 9px;">펜 굵기</label>
+                                    <input type="color" v-model="color" />
+                                    <input type="range" id="brushSize" min="1" max="10" v-model="brushSize"
+                                        @input="updateBrushSize" style="width: 100px; margin-left: 5px;" />
+                                    <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button>
+                                    <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button>
+                                    <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button>
+                                </div>
+                            </div>
+
+                            <div class="stickers" v-show="stickersVisible">
+                                <button><img src="../../../resources/img/img146_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img147_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img148_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img149_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img150_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img151_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img152_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img153_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img154_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img155_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img156_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img157_75s.png" alt=""></button>
+                                <button><img src="../../../resources/img/img158_75s.png" alt=""></button>
+                            </div>
+                        </div>
+                        <div>
+                            <div class="content" style="height: 549px; 
+                                position: relative;
+                                width: 973px; 
+                                display: flex; 
+                                justify-content: center; 
+                                align-items: center;">
+                                <canvas ref="canvas" style="position: absolute;"></canvas>
+                            </div>
+                            <div class="btn-wrap flex justify-center mt40" style="gap: 40px;">
+                                <button class="login-btn" @click="openCameraModal">
+                                    <img src="../../../resources/img/btn07_s.png" alt="">
+                                    <p>재촬영</p>
+                                </button>
+
+                                <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')">
+                                    <img src="../../../resources/img/btn07_s.png" alt="">
+                                    <p>완성</p>
+                                </button>
+                            </div>
+                        </div>
+                        <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
+                            <div class="mb20">
+                                <p class="popup-title" style="font-size: 32px">랜덤 단어</p>
+                            </div>
+                            <div class="flex-column" style="gap: 10px;">
+                                <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt="">
+                                    <p class="title">a</p>
+                                </button>
+                                <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt="">
+                                    <p class="title">a</p>
+                                </button>
+                                <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt="">
+                                    <p class="title" style="color: #fff;">a</p>
+                                </button>
+                                <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt="">
+                                    <p class="title mt20" style="color: #fff;">a</p>
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <!-- <div class="box">
+                        <div style="width: 100%;">
+                            <div id="container">
+                                <canvas ref="canvas"></canvas>
+                            </div>
+                        </div>
+                    </div> -->
+
+                </div>
+            </article>
+        </div>
+         <div class="complete-wrap  myphoto">
+            <button  class="login-btn mt10" type="submit" style="width: 100%;" @click="goToPage('MyPlan')"><img src="../../../resources/img/btn07_s.png" alt="" style="width: 100%; height: 100px;"> <p>학습 종료하기</p></button>
+            <h2 class="mb40 mt10">이 단원을 끝낸 친구들</h2>
+            <article class=" flex-column" style="gap: 5px;">
+                <div class="flex" style="gap: 5px;">
+                    <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div>
+                    <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div>
+                </div>
+            </article>
+            <article class="popup-wrap" v-show="searchOpen">
+                <div class="popup-box ">
+                    <div class="flex mb10  justify-between">
+                        <p class="popup-title">알림</p>
+                        <button type="button" class="popup-close-btn" @click="closeModal">
+                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                        </button>
+                    </div>
+                    <div class="box">
+                        <div style="width: 910px;"><img src="../../../resources/img/img140_747s.png" alt=""></div>
+                    </div>
+                    <div class="flex justify-between mt20">
+                        <div class="text  flex">
+                            <p class="title2 date ml30">2024-08-06</p>
+                            <span class=" title1 ml30">1단원을 마친 <em class="yellow">가나다</em>친구</span>
+                        </div>
+                        <div class="title2 flex align-center" style="gap: 10px;"><svg-icon type="mdi" :path="mdiHeart"
+                                style="color: #FFBA08;"></svg-icon>
+                            <p><em class="yellow">1</em></p>
+                        </div>
+                    </div>
+                </div>
+            </article>
+        </div> 
+
+    </div>
+</template>
+
+<script>
+import SvgIcon from '@jamescoyle/vue-icon';
+import { mdiMagnify, mdiHeart, mdiWindowClose } from '@mdi/js';
+
+export default {
+    data() {
+        return {
+            items: [
+                {
+                    imgSrc1: 'client/resources/img/img11_1_s.png',
+                    imgSrc2: 'client/resources/img/img12_1_s.png',
+                    imgSrc3: 'client/resources/img/img11_2_s.png',
+                    imgSrc4: 'client/resources/img/img12_2_s.png',
+                    isSecondImageVisible: false
+                },
+            ],
+            items_photo: [
+                {
+                    imgSrc1: 'client/resources/img/btn20_75s_normal.png',   //펜 선택되지 않음
+                    imgSrc2: 'client/resources/img/btn20_75s_click.png' //펜 선택됨
+                },
+                {
+                    imgSrc1: 'client/resources/img/btn21_75s_normal.png',   //스티커 선택되지 않음
+                    imgSrc2: 'client/resources/img/btn21_75s_click.png'     //스티커 선택됨
+                },
+            ],
+            mdiWindowClose: mdiWindowClose,
+            mdiHeart: mdiHeart,
+            showModal: false,
+            searchOpen: false,  // 사진 상세보기 모달창
+            searchOpen2: false, // 단원 마친 후, 사진 촬영 여부 선택 모달창
+            showCameraModal: false, // 카메라 모달창
+            showPhotoModal: false, // 사진꾸미기 모달창
+            photoTaken: false,
+            photo: null,    //캡쳐 사진
+            videoReady: false, // 비디오 준비 상태를 나타내는 플래그
+            stream: null,
+            canvasWidth: 0,
+            canvasHeight: 0,
+            selectedIndex: 0,   //툴 선택 여부 인덱스
+            stickersVisible: false, // 스티커 표시 여부 
+
+            //사진 꾸미기 관련 변수
+            drawHistory: [],    //도형 기록
+            tempLines: [],  //펜 기록
+            stickers: [],   //스티커 파일 기록
+            draggingStickerIndex: null, //스티커 드래그
+            activeStickerIndex: null,  // 현재 활성화된 스티커의 인덱스
+            nextLineId: 0,  //획 아이디
+            tool: 'draw',   //툴 결정
+            color: '#000000',   //펜 기본 색상
+            isDrawing: false,   //그리는 중인지 판단하는 변수
+            brushSize: 5,  // 초기 펜 굵기
+            startX: 0,
+            startY: 0,
+            canvasRect: {
+                topLeft: { x: 0, y: 0 },
+                bottomRight: { x: 0, y: 0 }
+            }
+
+        }
+    },
+    methods: {
+        toggleImage(index) {
+            this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible;
+        },
+        toggleImageAndShowPopup(index, dataNum) {
+            this.toggleImage(index);
+            if (dataNum === '11') {  // 최종 평가 버튼 클릭 시
+                this.searchOpen2 = true;  // 모달창 열기
+            }
+        },
+        ShowPopup() {
+            this.searchOpen2 = true;  // 촬영 여부 묻는 모달창 열기
+        },
+        updateContent(index) {
+            this.selectedIndex = index;
+
+            // 선택된 버튼이 스티커 버튼(인덱스 1)인지 확인
+            if (index === 1) {
+                this.stickersVisible = true;  // 스티커 툴 보이기
+            } else {
+                this.stickersVisible = false; // 스티커 툴 숨기기
+            }
+        },
+        goToPage(page) {
+            this.$router.push({ name: page });
+        },
+        openCameraModal() {
+            this.closeModal();
+            this.closePhotoModal();
+
+            this.drawHistory = [];
+            this.stickers = [];
+            this.tempLines = [];
+            this.videoReady = false; // 비디오 준비 상태 초기화
+
+            this.showCameraModal = true;
+            navigator.mediaDevices.getUserMedia({ video: true })
+                .then(stream => {
+                    const modalVideo = this.$refs.modalVideoElement;
+                    modalVideo.srcObject = stream;
+                    this.stream = stream;
+                    modalVideo.addEventListener('loadedmetadata', this.adjustContainerSize);
+                })
+                .catch(error => {
+                    console.log("error>>>>>>>>", error);
+                });
+        },
+        closeModal() {  //웹캠 및 모든 팝업 닫기
+            // this.showModal = false;
+            this.searchOpen = false;
+            this.searchOpen2 = false;
+            this.showCameraModal = false;
+            this.photoTaken = false;
+            this.photo = null;
+            this.showPhotoModal = false;
+
+            //스트림 종료
+            if (this.stream) {
+                let tracks = this.stream.getTracks();
+                tracks.forEach(track => track.stop());
+                this.stream = null;
+            }
+        },
+        closePhotoModal() { //사진꾸미기 팝업 닫기
+            this.showPhotoModal = false;
+            this.closeModal();
+        },
+        onVideoLoaded() {
+            this.videoReady = true;
+            this.adjustContainerSize();
+        },
+        adjustContainerSize() {
+            const video = this.$refs.modalVideoElement;
+            const container = this.$refs.container;
+            const body = this.$refs.body;
+            if (video && container) {
+                container.style.width = `${video.videoWidth}px`;
+                container.style.height = `${video.videoHeight}px`;
+                body.style.height = `${video.videoHeight}px`;
+            }
+        },
+
+        buttonSearch() {
+            this.searchOpen = true;
+        },
+        buttonSearch2() {
+            this.searchOpen2 = true;
+        },
+        closeBtn() {
+            this.searchOpen = false;
+        },
+        capturePhoto() {
+            // 사진 촬영 기능 구현
+            console.log("cam on");
+
+            if (!this.videoReady) return; // 비디오가 준비되지 않았으면 사진을 찍지 않음
+
+            const video = this.$refs.modalVideoElement;
+            const canvas = document.createElement('canvas');
+
+            canvas.width = video.videoWidth;
+            canvas.height = video.videoHeight;
+            this.canvasWidth = video.videoWidth;
+            this.canvasHeight = video.videoHeight;
+            const context = canvas.getContext('2d');
+            context.translate(canvas.width, 0);
+            context.scale(-1, 1);
+            context.drawImage(video, 0, 0, canvas.width, canvas.height);
+            this.photo = canvas.toDataURL('image/png');
+            this.photoTaken = true;
+            this.showPhotoModal = true;
+            console.log("PhotoModal open");
+            this.$nextTick(() => {
+                console.log("canvas setup");
+                // console.log("Photo data>>>>", this.photo);
+                this.setupCanvas();
+            });
+
+        },
+        setupCanvas() {
+            const canvas = this.$refs.canvas;
+            // const container = this.$refs.container;
+            if (!canvas) {
+                console.error("Canvas reference not found");
+                resolve();
+                return;
+            }
+            const context = canvas.getContext('2d');
+            if (!context) {
+                console.error("Canvas context not found");
+                return;
+            }
+            const image = new Image();
+            image.src = this.photo;
+            // console.log("Photo data>>>>", image.src);
+            image.onload = () => {
+                console.log("Image loaded successfully");
+                // this.canvasWidth = image.width;
+                // this.canvasHeight = image.height;
+                //이미지 크기가 캔버스와 안맞으면 이미지 불러오는데에 에러 남
+                // container.style.width = this.canvasWidth;
+                // container.style.height = this.canvasHeight;
+
+                canvas.width = this.canvasWidth;
+                canvas.height = this.canvasHeight;
+
+                const rect = canvas.getBoundingClientRect();
+                // 좌측 상단 좌표
+                const topLeft = {
+                    x: rect.left,
+                    y: rect.top
+                };
+
+                // 우측 하단 좌표
+                const bottomRight = {
+                    x: rect.right,
+                    y: rect.bottom
+                };
+
+                // 캔버스 크기 초기화
+                this.updateCanvasRect();
+
+                // 윈도우 리사이즈 이벤트 리스너 추가
+                window.addEventListener('resize', this.updateCanvasRect);
+
+                // 클릭 이벤트 핸들러 추가
+                this.$refs.canvas.addEventListener('click', this.handleCanvasClick);
+
+
+                // canvas.width = canvas.clientWidth;
+                // canvas.height = canvas.clientHeight;
+                // this.canvasWidth = canvas.clientWidth;
+                // this.canvasHeight = canvas.clientHeight;
+
+                context.clearRect(0, 0, canvas.width, canvas.height);  // 이전 이미지 있으면 초기화
+                context.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight);
+                this.addCanvasEventListeners(); //추가해야함
+            };
+            image.onerror = (error) => {
+                console.error("Error loading image: ", error);
+            };
+        },
+
+        addCanvasEventListeners() {
+            const canvas = this.$refs.canvas;
+            canvas.addEventListener('mousedown', this.onMouseDown);
+            canvas.addEventListener('mouseup', this.onMouseUp);
+            canvas.addEventListener('mousemove', this.onMouseMove);
+            canvas.addEventListener('click', this.onCanvasClick);
+        },
+        setTool(tool) {
+            this.tool = tool;
+        },
+        updateBrushSize() {
+            // 펜 굵기 변경 로직
+            if (this.tool === 'draw') {
+                this.setBrushSize(this.brushSize);
+            }
+        },
+        setBrushSize(size) {
+            this.brushSize = size;
+            const context = this.$refs.canvas.getContext('2d');
+            context.lineWidth = size;
+        },
+        // 캔버스 크기 갱신 함수
+        updateCanvasRect() {
+            const rect = this.$refs.canvas.getBoundingClientRect();
+            this.canvasRect = {
+                topLeft: { x: rect.left, y: rect.top },
+                bottomRight: { x: rect.right, y: rect.bottom }
+            };
+            console.log(">>>>>>>>>2222", rect.left);
+        },
+        getCanvasPosition(event) {
+
+            const rect = this.canvasRect;
+            console.log(">>>>>>>>>", this.canvasRect);
+            this.updateCanvasRect();
+
+            // 윈도우 리사이즈 이벤트 리스너 추가
+            window.addEventListener('resize', this.updateCanvasRect);
+
+            // 클릭 이벤트 핸들러 추가
+            this.$refs.canvas.addEventListener('click', this.handleCanvasClick);
+
+            // // 좌측 상단 좌표
+            // const topLeft = {
+            //     x: rect.left,
+            //     y: rect.top
+            // };
+
+            // // 우측 하단 좌표
+            // const bottomRight = {
+            //     x: rect.right,
+            //     y: rect.bottom
+            // };
+
+            // console.log(this.scrollLeft)
+
+            const x = event.clientX - rect.topLeft.x
+            const y = event.clientY - rect.topLeft.y
+
+
+            console.log(`클릭한 좌표: x=${event.clientX}, y=${event.clientY}`);
+            console.log(`계산베이스 좌표: x=${rect.topLeft.x}, y=${rect.topLeft.y}`);
+            console.log(`계산베이스 좌표: x=${rect.topLeft.x}, y=${rect.topLeft.y}`);
+            console.log(`계산된 좌표: x=${x}, y=${y}`);
+            return {
+                x, y
+            };
+        },
+        onMouseDown(event) {
+
+            // 캔버스 크기 초기화
+            this.updateCanvasRect();
+
+            // 윈도우 리사이즈 이벤트 리스너 추가
+            window.addEventListener('resize', this.updateCanvasRect);
+
+            // 클릭 이벤트 핸들러 추가
+            this.$refs.canvas.addEventListener('click', this.handleCanvasClick);
+
+            const { x, y } = this.getCanvasPosition(event);
+            this.startX = x;
+            this.startY = y;
+            const context = this.$refs.canvas.getContext('2d');
+            context.strokeStyle = this.color;
+            context.lineWidth = this.brushSize;  // 브러시 크기 설정
+            if (this.tool === 'draw') {
+                context.beginPath();
+                context.moveTo(this.startX, this.startY);
+                this.nextLineId++;
+            }
+            this.isDrawing = true;
+        },
+        onMouseUp(event) {
+            if (!this.isDrawing) return;
+            const { x, y } = this.getCanvasPosition(event);
+            const context = this.$refs.canvas.getContext('2d');
+            context.strokeStyle = this.color;
+            context.lineWidth = this.brushSize;  // 브러시 크기 설정
+            if (this.tool === 'rectangle') {
+                context.strokeRect(this.startX, this.startY, x - this.startX, y - this.startY);
+                this.drawHistory.push({ type: 'rectangle', startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color });
+            } else if (this.tool === 'circle') {
+                context.beginPath();
+                const radius = Math.sqrt(Math.pow((x - this.startX), 2) + Math.pow((y - this.startY), 2));
+                context.arc(this.startX, this.startY, radius, 0, 2 * Math.PI);
+                context.stroke();
+                this.drawHistory.push({ type: 'circle', startX: this.startX, startY: this.startY, radius, color: this.color });
+            } else if (this.tool === 'draw') {
+                context.lineTo(x, y);
+                context.stroke();
+                this.tempLines.push({ id: this.nextLineId, startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color });
+            }
+            this.isDrawing = false;
+        },
+        onMouseMove(event) {
+            if (!this.isDrawing || this.tool !== 'draw') return;
+            const { x, y } = this.getCanvasPosition(event);
+            const context = this.$refs.canvas.getContext('2d');
+            context.strokeStyle = this.color;
+            context.lineWidth = this.brushSize;  // 브러시 크기 설정
+            context.lineTo(x, y);
+            context.stroke();
+            this.tempLines.push({ id: this.nextLineId, startX: this.startX, startY: this.startY, endX: x, endY: y, color: this.color });
+            this.startX = x;
+            this.startY = y;
+        },
+        onCanvasClick(event) {
+            if (this.tool === 'eraser') {
+                const { x, y } = this.getCanvasPosition(event);
+                this.eraseDrawing(x, y);
+            }
+        },
+        eraseDrawing(x, y) {
+            const eraserRadius = 10;
+            this.drawHistory = this.drawHistory.filter(item => {
+                if (item.type === 'rectangle') {
+                    return !(x >= item.startX && x <= item.endX && y >= item.startY && y <= item.endY);
+                } else if (item.type === 'circle') {
+                    const distance = Math.sqrt(Math.pow((x - item.startX), 2) + Math.pow((y - item.startY), 2));
+                    return !(distance <= item.radius);
+                }
+            });
+            const linesToDelete = this.tempLines.filter(line => {
+                const distanceToLine = this.distanceToLineSegment(line.startX, line.startY, line.endX, line.endY, x, y);
+                return distanceToLine <= 10;
+            }).map(line => line.id);
+            this.tempLines = this.tempLines.filter(line => !linesToDelete.includes(line.id));
+            this.redraw();
+        },
+        distanceToLineSegment(x1, y1, x2, y2, px, py) {
+            const lengthSquared = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2);
+            if (lengthSquared === 0) return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2));
+            const t = Math.max(0, Math.min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / lengthSquared));
+            const projX = x1 + t * (x2 - x1);
+            const projY = y1 + t * (y2 - y1);
+            return Math.sqrt(Math.pow(px - projX, 2) + Math.pow(py - projY, 2));
+        },
+        clearAll() {
+            this.drawHistory = [];
+            this.stickers = [];
+            this.tempLines = [];
+            this.redraw();
+        },
+        redraw() {
+            const canvas = this.$refs.canvas;
+            const context = canvas.getContext('2d');
+            const image = new Image();
+            image.src = this.photo;
+            image.onload = () => {
+                context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
+                context.drawImage(image, 0, 0, this.canvasWidth, this.canvasHeight);
+                this.drawHistory.forEach(item => {
+                    context.strokeStyle = item.color;
+                    if (item.type === 'draw') {
+                        context.beginPath();
+                        context.moveTo(item.startX, item.startY);
+                        context.lineTo(item.endX, item.endY);
+                        context.stroke();
+                    } else if (item.type === 'rectangle') {
+                        context.strokeRect(item.startX, item.startY, item.endX - item.startX, item.endY - item.startY);
+                    } else if (item.type === 'circle') {
+                        context.beginPath();
+                        context.arc(item.startX, item.startY, item.radius, 0, 2 * Math.PI);
+                        context.stroke();
+                    }
+                });
+                this.tempLines.forEach(line => {
+                    context.strokeStyle = line.color;
+                    context.beginPath();
+                    context.moveTo(line.startX, line.startY);
+                    context.lineTo(line.endX, line.endY);
+                    context.stroke();
+                });
+                this.stickers.forEach((sticker, index) => {
+                    context.drawImage(sticker.img, sticker.x, sticker.y, sticker.width, sticker.height);
+                });
+            };
+        },
+
+
+    },
+    components: {
+        SvgIcon,
+    },
+    mounted() {
+        console.log('main mounted');
+
+
+    },
+    computed() {
+
+    },
+    beforeDestroy() {
+        // 컴포넌트가 파괴되기 전에 리스너 제거
+        window.removeEventListener('resize', this.updateCanvasRect);
+        this.$refs.canvas.removeEventListener('click', this.handleCanvasClick);
+    }
+}
+</script>
+
+<style>
+.body {
+    width: 1435px;
+    height: auto;
+    margin: 0 auto;
+}
+
+#container {
+    position: relative;
+    margin: auto;
+    border: 10px #333 solid;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 100;
+}
+
+
+video {
+    width: 100%;
+    height: auto;
+    background-color: #666;
+}
+
+.mirrored {
+    transform: scaleX(-1);
+}
+
+.new-btn:disabled {
+    background-color: #FFF3D7;
+    cursor: not-allowed;
+}
+
+/* button {
+    margin: auto;
+    padding: 5px 10px;
+    font-size: 13px;
+    cursor: pointer;
+    display: flex;
+    justify-content: center;
+    text-align: center;
+} */
+
+.sticker {
+    position: absolute;
+    cursor: move;
+}
+
+.sticker-handle {
+    width: 15px;
+    height: 15px;
+    background: rgba(255, 255, 255, 0.521);
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    cursor: nwse-resize;
+    font-size: 13px;
+    font-weight: bolder;
+    color: rgb(63, 63, 63);
+}
+
+.sticker-delete {
+    position: absolute;
+    top: 0;
+    right: 0;
+    background: rgba(255, 0, 0, 0.425);
+    color: white;
+    padding: 5px;
+    cursor: pointer;
+}
+
+.toolbar {
+    display: flex;
+    justify-content: center;
+    margin-top: 10px;
+}
+
+.toolbar button {
+    margin: 5px;
+    padding: 5px 10px;
+    cursor: pointer;
+}
+
+.toolbar input {
+    margin: 5px;
+}
+
+.rabbit-end {
+    cursor: pointer;
+}
+</style>(No newline at end of file)
client/views/pages/main/Chapter/Chapter1.vue
--- client/views/pages/main/Chapter/Chapter1.vue
+++ client/views/pages/main/Chapter/Chapter1.vue
@@ -17,10 +17,12 @@
             </div>
             <div class="next-btn"  @click="goToPage('Chapter1_1')"><img src="../../../../resources/img/right.png" alt=""></div>
         </div>
+        <TextToImage/>
     </div>
 </template>
 
 <script>
+import TextToImage from '../../../component/TextToImage.vue';
 export default {
     data() {
         return {
@@ -38,6 +40,7 @@
 
     },
     components: {
+        TextToImage : TextToImage,
     },
     mounted() {
     }
client/views/pages/main/Chapter/Chapter1_2.vue
--- client/views/pages/main/Chapter/Chapter1_2.vue
+++ client/views/pages/main/Chapter/Chapter1_2.vue
@@ -49,10 +49,12 @@
       </div>
       <div class="next-btn" @click="goToPage('Chapter1_3')"><img src="../../../../resources/img/right.png" alt=""></div>
     </div>
+    <TextToImage/>
   </div>
 </template>
 
 <script>
+import TextToImage from '../../../component/TextToImage.vue';
 export default {
   data() {
     return {
@@ -70,6 +72,7 @@
 
   },
   components: {
+    TextToImage : TextToImage
   },
   mounted() {
     const textArea = this.$refs.textArea;
client/views/pages/main/Chapter/Chapter1_3.vue
--- client/views/pages/main/Chapter/Chapter1_3.vue
+++ client/views/pages/main/Chapter/Chapter1_3.vue
@@ -20,10 +20,12 @@
             </div>
             <div class="next-btn"  @click="goToPage('Dashboard')"><img src="../../../../resources/img/right.png" alt=""></div>
         </div>
+        <TextToImage/>
     </div>
 </template>
 
 <script>
+import TextToImage from '../../../component/TextToImage.vue';
 export default {
     data() {
         return {
@@ -41,8 +43,12 @@
 
     },
     components: {
+        TextToImage : TextToImage
     },
     mounted() {
     }
 }
-</script>
(No newline at end of file)
+</script>
+<style scoped>
+.listenGroup .textbox { width: 900px};
+</style>
(No newline at end of file)
client/views/pages/main/Chapter/Chapter2_2.vue
--- client/views/pages/main/Chapter/Chapter2_2.vue
+++ client/views/pages/main/Chapter/Chapter2_2.vue
@@ -1,31 +1,40 @@
 <template>
-  <div id="Chapter2_2" class="content-wrap">
-     <div class="title-box mb25 flex align-center mt40">
-        <span class="title mr40">1. Hello WORLD</span>
-        <span class="subtitle">my name is dd</span>
-     </div>
-     <div class="flex justify-between align-center">
-        <div class="pre-btn" @click="goToPage('Chapter2_1')"><img src="../../../../resources/img/left.png" alt=""></div>
-        <div class="content title-box">
-           <p class="title mt25 title-bg">step2</p>
-          <div class="flex align-center mb30">
-          <p class="subtitle2 mr20">카드를 뒤집어 보세요.</p>
-          <!-- <button><img src="../../../../resources/img/btn10_s.png" alt="">
+   <div id="Chapter2_2" class="content-wrap">
+      <div class="title-box mb25 flex align-center mt40">
+         <span class="title mr40">1. Hello WORLD</span>
+         <span class="subtitle">my name is dd</span>
+      </div>
+      <div class="flex justify-between align-center">
+         <div class="pre-btn" @click="goToPage('Chapter2_1')"><img src="../../../../resources/img/left.png" alt="">
+         </div>
+         <div class="content title-box">
+            <p class="title mt25 title-bg">step2</p>
+            <div class="flex align-center mb30">
+               <p class="subtitle2 mr20">카드를 뒤집어 보세요.</p>
+               <!-- <button><img src="../../../../resources/img/btn10_s.png" alt="">
           </button> -->
-        </div>
+            </div>
 
-           <div class="imgGroup">
-              <div class="flex justify-center" style="gap: 90px;">
-                 <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)">
-                    <img :src="item.imgSrc1" >
-                    <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
-                 </button>
-              </div>
-           </div>
-        </div>
-        <div class="next-btn" @click="goToPage('Chapter2_3')"><img src="../../../../resources/img/right.png" alt=""></div>
-     </div>
-  </div>
+            <div class="imgGroup">
+               <div class="flex justify-center" style="gap: 90px;">
+                  <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)">
+                     <div class="listenGroup">
+                        <img :src="item.imgSrc1">
+                        <p class="textbox"><img :src="item.imgSrc"></p>
+
+                     </div>
+                     <div class="listenGroup" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        <img :src="item.imgSrc2">
+                        <p class="title4 textbox">{{ item.title }}</p>
+                     </div>
+                  </button>
+               </div>
+            </div>
+         </div>
+         <div class="next-btn" @click="goToPage('Chapter2_3')"><img src="../../../../resources/img/right.png" alt="">
+         </div>
+      </div>
+   </div>
 </template>
 
 
@@ -34,10 +43,10 @@
    data() {
       return {
          items: [
-            { imgSrc1: 'client/resources/img/img49_s_1.png', imgSrc2: 'client/resources/img/img50_s_2.png', isSecondImageVisible: false },
-            { imgSrc1: 'client/resources/img/img49_s_2.png', imgSrc2: 'client/resources/img/img50_s_3.png', isSecondImageVisible: false },
-            { imgSrc1: 'client/resources/img/img49_s_3.png', imgSrc2: 'client/resources/img/img50_s_4.png', isSecondImageVisible: false },
-            { imgSrc1: 'client/resources/img/img49_s_4.png', imgSrc2: 'client/resources/img/img50_s_1.png', isSecondImageVisible: false }
+            { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' },
+            { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' },
+            { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' },
+            { imgSrc1: 'client/resources/img/img49_s.png', imgSrc2: 'client/resources/img/img50_s.png', imgSrc: 'client/resources/img/img59_36s.png', isSecondImageVisible: false, title: 'a' }
          ]
       };
    },
@@ -51,4 +60,21 @@
    }
 }
 
-</script>
(No newline at end of file)
+</script>
+<style scoped>
+.popTxt {
+   width: 295px;
+   height: 406px;
+}
+
+.popTxt div:last-child img {
+   position: absolute;
+   top: 0;
+   left: 0;
+}
+
+.listenGroup .textbox {
+   top: 50%;
+    left: 50%;
+}
+</style>
(No newline at end of file)
client/views/pages/main/Chapter/Chapter2_4.vue
--- client/views/pages/main/Chapter/Chapter2_4.vue
+++ client/views/pages/main/Chapter/Chapter2_4.vue
@@ -19,11 +19,15 @@
             <div class="imgGroup">
 
                <div class="flex justify-center">
-                  <div class="btnGroup  flex" style="gap: 60px;">
+                  <div class="listenGroup  flex" style="gap: 60px;">
                      <button class="popTxt" v-for="(item, index) in items" :key="index" @click="updateContent(index)"
                         :class="{ active: selectedIndex === index }">
                         <img :src="item.imgSrc1">
                         <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;">
+                        <div class="textbox">
+                          <div style="height: 80%; line-height: 200px;"> <img :src="item.imgSrc" alt=""></div>
+                           <p class="subtitle3" style="height: 20%;">{{ item.title }}</p>
+                        </div>
                      </button>
                   </div>
                </div>
@@ -43,10 +47,10 @@
    data() {
       return {
          items: [
-            { imgSrc1: 'client/resources/img/img61_36s_1.png', imgSrc2: 'client/resources/img/img61_36s_1_Click.png' },
-            { imgSrc1: 'client/resources/img/img61_36s_2.png', imgSrc2: 'client/resources/img/img61_36s_2_Click.png' },
-            { imgSrc1: 'client/resources/img/img61_36s_3.png', imgSrc2: 'client/resources/img/img61_36s_4_Click.png' },
-            { imgSrc1: 'client/resources/img/img61_36s_4.png', imgSrc2: 'client/resources/img/img61_36s_3_Click.png' },
+            { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img59_36s.png', title: 'apple' },
+            { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img58_36s.png', title: 'a'},
+            { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img57_36s.png', title: 'a' },
+            { imgSrc1: 'client/resources/img/img61_36s.png', imgSrc2: 'client/resources/img/img62_36s.png', imgSrc: 'client/resources/img/img56_36s.png', title: 'a' },
          ],
          selectedIndex: 0,
          timer: "00",
@@ -68,10 +72,17 @@
 </script>
 
 <style scoped>
+.listenGroup{height: 305px;}
+.listenGroup button{position: relative;}
+.listenGroup .textbox {    
+   width: 184px;
+    height: 206px;
+    left: 46%;
+    top: 43%;} 
 .look-text{bottom: 50px;}
 .btnGroup{width: 1060px; height: 340px;}
-.popTxt{width: 216px;}
-.popTxt img {
+.popTxt{width: 216px; }
+.popTxt > img {
    position: absolute;
    top: 0;
    left: 0;
client/views/pages/main/Chapter/Chapter2_9.vue
--- client/views/pages/main/Chapter/Chapter2_9.vue
+++ client/views/pages/main/Chapter/Chapter2_9.vue
@@ -17,15 +17,67 @@
           <div class="text-ct">
           <div class="dropGroup flex align-center justify-center">
             <div style="position: relative;" >
-              <img src="../../../../resources/img/img72_41s_1.png" alt="" style="width: 90%;">
-              <button style="left: 141px;
-    top: 60px;"><img src="../../../../resources/img/img78_41s.png" alt=""></button>
-              <button style="    left: 200px;
-    bottom: 86px;"><img src="../../../../resources/img/img79_41s.png" alt=""></button>
-              <button style="    right: 237px;
-    top: 159px;"><img src="../../../../resources/img/img80_41s.png" alt=""></button>
-              <button style="     right: 159px;
-    bottom: 46px;"><img src="../../../../resources/img/img81_41s.png" alt=""></button>
+              <img src="../../../../resources/img/img72_41s.png" alt="" style="width: 93%;">
+              <div class="vocaGroup">
+                <div class="flex justify-between mb80">
+                    <article class="flex align-center">
+                        <div class="imgGroup mr30"><img src="../../../../resources/img/img74_41s.png" data-num="1"></div>
+                        <div class="flex align-start">
+                             <div>
+                                <h3>sidewalk</h3>
+                                   <div class="flex align-center mt10">
+                                      <span class="title1">보도</span>
+                                   </div>
+                           </div>
+                        </div>
+                    </article>
+                    <article class="flex align-center">
+                        <div class="imgGroup mr30"><img src="../../../../resources/img/img75_41s.png" data-num="1"></div>
+                        <div class="flex align-start">
+                             <div>
+                                <h3>sidewalk</h3>
+                                   <div class="flex align-center mt10">
+                                      <span class="title1">보도</span>
+                                   </div>
+                           </div>
+                        </div>
+                    </article>
+                </div>
+                <div class="flex justify-between">
+                    <article class="flex align-center">
+                        <div class="imgGroup mr30"><img src="../../../../resources/img/img76_41s.png" data-num="1"></div>
+                        <div class="flex align-start">
+                             <div>
+                                <h3>sidewalk</h3>
+                                   <div class="flex align-center mt10">
+                                      <span class="title1">보도</span>
+                                   </div>
+                           </div>
+                        </div>
+                    </article>
+                    <article class="flex align-center">
+                        <div class="imgGroup mr30"><img src="../../../../resources/img/img77_41s.png" data-num="1"></div>
+                        <div class="flex align-start">
+                             <div>
+                                <h3>sidewalk</h3>
+                                   <div class="flex align-center mt10">
+                                      <span class="title1">보도</span>
+                                   </div>
+                           </div>
+                        </div>
+                    </article>
+                </div>
+              </div>
+             <div class="textbox">
+                <button style="left: 141px;
+      top: 44px;"><img src="../../../../resources/img/img78_41s.png" alt=""></button>
+                <button style="    left: 200px;
+      bottom: 46px;"><img src="../../../../resources/img/img79_41s.png" alt=""></button>
+                <button style="    right: 237px;
+      top: 114px;"><img src="../../../../resources/img/img80_41s.png" alt=""></button>
+                <button style="     right: 159px;
+      bottom: 5px;"><img src="../../../../resources/img/img81_41s.png" alt=""></button>
+             </div>
                
             </div>
           </div>
@@ -68,4 +120,8 @@
   }
   </script>
   <style scoped>
+  .vocaGroup{    width: 79%;
+    position: absolute;
+    top: 90px;
+    left: 120px;}
 </style>
(No newline at end of file)
client/views/pages/main/Dashboard.vue
--- client/views/pages/main/Dashboard.vue
+++ client/views/pages/main/Dashboard.vue
@@ -1,11 +1,28 @@
 <template>
-    <div class="main">
-        <div class="race-wrap">
-            <div class="title-box">
-                <p class="title">hello world!</p>
-                <p class="subtitle">hi my name is dd!</p>
-            </div>
-            <div class="race-box">
+    <div>
+        <div v-if="roadViewTF">
+            <!-- 1번 템플릿 -->
+            <div class="main">
+                <div class="race-wrap">
+                    <div class="title-box">
+                        <p class="title">hello world!</p>
+                        <p class="subtitle">hi my name is dd!</p>
+                    </div>
+                    <!--
+                    <div class="race-box">
+                        <div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div>
+                        <div class="rcon flex justify-between mb5">
+                            <div class="race-btn" v-for="roadmap in roadmapData" :key="roadmap.learning_id" @click="goToPage('Chapter7')">
+                                <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)" data-num="2">
+                                    <img :src="item.imgSrc1">
+                                    <img :src="item.imgSrc2" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                                </button>
+                                <p>{{getNonNullColumn(roadmap)}}</p>
+                            </div>
+                        </div>
+                    </div>
+                -->
+                <div class="race-box">
                 <div class="rabbit-start"><img src="../../../resources/img/img09_s.png" alt=""></div>
                 <div class="rcon flex justify-end mb5">
                     <div class="race-btn" @click="goToPage('Chapter1')">
@@ -92,16 +109,16 @@
                         </button>
                         <p>단어장</p>
                     </div>
-                    <div class="rcon flex">
-                        <div class="race-btn">
-                            <button class="popTxt" v-for="(item, index) in items" :key="index"
-                                @click="toggleImageAndShowPopup(index, '11')">
-                                <img :src="item.imgSrc3">
-                                <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
-                            </button>
-                            <p class="long">최종 평가</p>
-                        </div>
+
+                    <div class="race-btn">
+                        <button class="popTxt" v-for="(item, index) in items" :key="index" @click="toggleImage(index)"
+                        data-num="11">
+                            <img :src="item.imgSrc3">
+                            <img :src="item.imgSrc4" :style="{ display: item.isSecondImageVisible ? 'block' : 'none' }">
+                        </button>
+                        <p class="long">최종 평가</p>
                     </div>
+
                 </div>
             </div>
             <!-- 팝업 -->
@@ -112,157 +129,146 @@
                         <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
                     </button>
 
-                    <div class="mb30 text-ct">
-                        <p class="title1 mb20">1단원이 끝났습니다!</p>
-                        <p class="title1"><em class="yellow">기념사진</em>을 촬영하러 가요</p>
-                    </div>
-                    <div class="flex justify-center">
-                        <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal">
-                            사진 촬영
-                        </button>
-                    </div>
-                </div>
-            </div>
-
-            <!-- 카메라 모달 -->
-            <article v-show="showCameraModal" class="popup-wrap">
-                <div class="popup-box" style="top: 500px; left:500px">
-                    <div class="flex mb10 justify-between">
-                        <p class="popup-title">사진 촬영</p>
-                        <button type="button" class="popup-close-btn" @click="closeModal">
-                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
-                        </button>
-                    </div>
-                    <div class="box">
-                        <div style="width: 100%;">
-                            <!-- 여기에 카메라 기능을 구현 -->
-                            <!-- <p>카메라 모듈이 여기에 위치합니다.</p> -->
-
-                            <div id="container" ref="container">
-                                <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored"
-                                    @canplay="onVideoLoaded"></video>
-                            </div>
-
+                        <div class="mb30 text-ct">
+                            <p class="title1 mb20">1단원이 끝났습니다!</p>
+                            <p class="title1"><em class="yellow">기념사진</em>을 촬영하러 가요</p>
+                        </div>
+                        <div class="flex justify-center">
+                            <button type="button" title="사진촬영" class="new-btn" @click="openCameraModal">
+                                사진 촬영
+                            </button>
                         </div>
                     </div>
-                    <div class="flex justify-center mt20">
-                        <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto"
-                            :disabled="!videoReady">
-                            사진 촬영
-                        </button>
-
-                    </div>
                 </div>
-            </article>
 
-            <!-- 사진 모달 -->
-            <article v-show="showPhotoModal" class="popup-wrap">
-                <div class="popup-box" style="top: 500px; left: auto">
-                    <div class="flex mb10 justify-between">
-                        <p class="popup-title">사진 꾸미기</p>
-                        <button type="button" class="popup-close-btn" @click="closePhotoModal">
-                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
-                        </button>
+                <!-- 카메라 모달 -->
+                <article v-show="showCameraModal" class="popup-wrap">
+                    <div class="popup-box" style="top: 500px; left:500px">
+                        <div class="flex mb10 justify-between">
+                            <p class="popup-title">사진 촬영</p>
+                            <button type="button" class="popup-close-btn" @click="closeModal">
+                                <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                            </button>
+                        </div>
+                        <div class="box">
+                            <div style="width: 100%;">
+                                <div id="container" ref="container">
+                                    <video v-if="!photoTaken" autoplay="true" ref="modalVideoElement" class="mirrored"
+                                        @canplay="onVideoLoaded"></video>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="flex justify-center mt20">
+                            <button type="button" class="new-btn" v-if="!photoTaken" @click="capturePhoto"
+                                :disabled="!videoReady">
+                                사진 촬영
+                            </button>
+                        </div>
                     </div>
-                    <div class="flex justify-between align-center" style="gap: 40px;">
-                        <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
-                            <div class="tool">
-                                <div class="flex justify-center mb20" style="gap: 20px;">
-                                    <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo"
-                                        :key="index" @click="updateContent(index)"
-                                        :class="{ active: selectedIndex === index }">
-                                        <img :src="item.imgSrc1" style="display: block;">
-                                        <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;">
+                </article>
+
+                <!-- 사진 모달 -->
+                <article v-show="showPhotoModal" class="popup-wrap">
+                    <div class="popup-box" style="top: 500px; left: auto">
+                        <div class="flex mb10 justify-between">
+                            <p class="popup-title">사진 꾸미기</p>
+                            <button type="button" class="popup-close-btn" @click="closePhotoModal">
+                                <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+                            </button>
+                        </div>
+                        <div class="flex justify-between align-center" style="gap: 40px;">
+                            <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
+                                <div class="tool">
+                                    <div class="flex justify-center mb20" style="gap: 20px;">
+                                        <button class="popTxt" style="width: 101px;" v-for="(item, index) in items_photo"
+                                            :key="index" @click="updateContent(index)"
+                                            :class="{ active: selectedIndex === index }">
+                                            <img :src="item.imgSrc1" style="display: block;">
+                                            <img :src="item.imgSrc2" v-if="selectedIndex === index" style="display: block;">
+                                        </button>
+                                    </div>
+                                </div>
+
+                                <div class="stickers" v-show="!stickersVisible">
+                                    <div class="toolbar">
+                                        <label for="brushSize" style="font-size: 9px;">펜 굵기</label>
+                                        <input type="color" v-model="color" />
+                                        <input type="range" id="brushSize" min="1" max="10" v-model="brushSize"
+                                            @input="updateBrushSize" style="width: 100px; margin-left: 5px;" />
+                                        <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button>
+                                        <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button>
+                                        <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button>
+                                    </div>
+                                </div>
+
+                                <div class="stickers" v-show="stickersVisible">
+                                    <button><img src="../../../resources/img/img146_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img147_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img148_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img149_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img150_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img151_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img152_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img153_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img154_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img155_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img156_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img157_75s.png" alt=""></button>
+                                    <button><img src="../../../resources/img/img158_75s.png" alt=""></button>
+                                </div>
+                            </div>
+                            <div>
+                                <div class="content" style="height: 549px; 
+                                    position: relative;
+                                    width: 973px; 
+                                    display: flex; 
+                                    justify-content: center; 
+                                    align-items: center;">
+                                    <canvas ref="canvas" style="position: absolute;"></canvas>
+                                </div>
+                                <div class="btn-wrap flex justify-center mt40" style="gap: 40px;">
+                                    <button class="login-btn" @click="openCameraModal">
+                                        <img src="../../../resources/img/btn07_s.png" alt="">
+                                        <p>재촬영</p>
+                                    </button>
+
+                                    <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')">
+                                        <img src="../../../resources/img/btn07_s.png" alt="">
+                                        <p>완성</p>
                                     </button>
                                 </div>
                             </div>
-
-                            <div class="stickers" v-show="!stickersVisible">
-                                <div class="toolbar">
-                                    <label for="brushSize" style="font-size: 9px;">펜 굵기</label>
-                                    <input type="color" v-model="color" />
-                                    <input type="range" id="brushSize" min="1" max="10" v-model="brushSize"
-                                        @input="updateBrushSize" style="width: 100px; margin-left: 5px;" />
-                                    <button class="new-btn" style="font-size: 9px;" @click="setTool('draw')">펜</button>
-                                    <button class="new-btn" style="font-size: 9px;" @click="setTool('eraser')">지우개</button>
-                                    <button class="new-btn" style="font-size: 9px;" @click="clearAll">전체 지우개</button>
+                            <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
+                                <div class="mb20">
+                                    <p class="popup-title" style="font-size: 32px">랜덤 단어</p>
                                 </div>
-                            </div>
-
-                            <div class="stickers" v-show="stickersVisible">
-                                <button><img src="../../../resources/img/img146_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img147_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img148_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img149_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img150_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img151_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img152_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img153_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img154_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img155_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img156_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img157_75s.png" alt=""></button>
-                                <button><img src="../../../resources/img/img158_75s.png" alt=""></button>
-                            </div>
-                        </div>
-                        <div>
-                            <div class="content" style="height: 549px; 
-                                position: relative;
-                                width: 973px; 
-                                display: flex; 
-                                justify-content: center; 
-                                align-items: center;">
-                                <canvas ref="canvas" style="position: absolute;"></canvas>
-                            </div>
-                            <div class="btn-wrap flex justify-center mt40" style="gap: 40px;">
-                                <button class="login-btn" @click="openCameraModal">
-                                    <img src="../../../resources/img/btn07_s.png" alt="">
-                                    <p>재촬영</p>
-                                </button>
-
-                                <button class="login-btn" type="submit" @click="goToPage('PhotoEdit')">
-                                    <img src="../../../resources/img/btn07_s.png" alt="">
-                                    <p>완성</p>
-                                </button>
-                            </div>
-                        </div>
-                        <div class="content" style="padding: 30px; min-width: 401px; min-height: 710px;">
-                            <div class="mb20">
-                                <p class="popup-title" style="font-size: 32px">랜덤 단어</p>
-                            </div>
-                            <div class="flex-column" style="gap: 10px;">
-                                <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt="">
-                                    <p class="title">a</p>
-                                </button>
-                                <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt="">
-                                    <p class="title">a</p>
-                                </button>
-                                <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt="">
-                                    <p class="title" style="color: #fff;">a</p>
-                                </button>
-                                <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt="">
-                                    <p class="title mt20" style="color: #fff;">a</p>
-                                </button>
+                                <div class="flex-column" style="gap: 10px;">
+                                    <button class="login-btn"><img src="../../../resources/img/img141_75s.png" alt="">
+                                        <p class="title">a</p>
+                                    </button>
+                                    <button class="login-btn"><img src="../../../resources/img/img152_75s_01.png" alt="">
+                                        <p class="title">a</p>
+                                    </button>
+                                    <button class="login-btn"><img src="../../../resources/img/img144_75s.png" alt="">
+                                        <p class="title" style="color: #fff;">a</p>
+                                    </button>
+                                    <button class="login-btn"><img src="../../../resources/img/img145_75s.png" alt="">
+                                        <p class="title mt20" style="color: #fff;">a</p>
+                                    </button>
+                                </div>
                             </div>
                         </div>
                     </div>
-                    <!-- <div class="box">
-                        <div style="width: 100%;">
-                            <div id="container">
-                                <canvas ref="canvas"></canvas>
-                            </div>
-                        </div>
-                    </div> -->
-
-                </div>
-            </article>
+                </article>
+            </div>
+        </div>
 
         </div>
         <div class="complete-wrap mt90 myphoto">
             <h2 class="mb40">이 단원을 끝낸 친구들</h2>
             <article class=" flex-column" style="gap: 5px;">
                 <div class="flex" style="gap: 5px;">
-                    <div @click="buttonSearch2" class="photo" ><img   src="../../../resources/img/img143_75s.png" alt=""></div>
+                    <div @click="buttonSearch2" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div>
                     <div @click="buttonSearch" class="photo"><img src="../../../resources/img/img143_75s.png" alt=""></div>
                 </div>
             </article>
@@ -275,29 +281,16 @@
                             <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
                         </button>
                     </div>
-                    <div class="box">
-                        <div style="width: 910px;"><img src="../../../resources/img/img140_747s.png" alt=""></div>
-                    </div>
-                    <div class="flex justify-between mt20">
-                        <div class="text  flex">
-                            <p class="title2 date ml30">2024-08-06</p>
-                            <span class=" title1 ml30">1단원을 마친 <em class="yellow">가나다</em>친구</span>
-                        </div>
-                        <div class="title2 flex align-center" style="gap: 10px;"><svg-icon type="mdi" :path="mdiHeart"
-                                style="color: #FFBA08;"></svg-icon>
-                            <p><em class="yellow">1</em></p>
-                        </div>
-                    </div>
                 </div>
             </article>
         </div>
-
     </div>
 </template>
 
 <script>
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiMagnify, mdiHeart, mdiWindowClose } from '@mdi/js';
+import axios from 'axios';
 
 export default {
     data() {
@@ -321,6 +314,7 @@
                     imgSrc2: 'client/resources/img/btn21_75s_click.png'     //스티커 선택됨
                 },
             ],
+            mdiMagnify: mdiMagnify,
             mdiWindowClose: mdiWindowClose,
             mdiHeart: mdiHeart,
             showModal: false,
@@ -353,19 +347,54 @@
             canvasRect: {
                 topLeft: { x: 0, y: 0 },
                 bottomRight: { x: 0, y: 0 }
-            }
+            },
 
+
+            roadViewTF : false,
+            roadmapData: [],
         }
     },
     methods: {
+        fetchRoadmapData() {
+            const vm = this;
+            axios({
+                url: "/unitLearning/find.json",
+                method: "post",
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    unit_id: "UNIT_000000000000001",
+                    book_id: "BOOK_000000000000004"
+                }
+            })
+            .then(response => {
+                    console.log("roadmap - response : ", response.data);
+                    vm.roadmapData = response.data;
+                    vm.roadViewTF = vm.roadmapData.length > 0;
+            })
+            .catch(error => {
+                console.error("Error fetching roadmap data:", error);
+            });
+        },
+        getNonNullColumn(item) {
+            if (item.prblm_id !== null) return '문제';
+            if (item.wd_book_id !== null) return '단어장';
+            if (item.text_id !== null) return '지문';
+            if (item.eval_id !== null) return '평가';
+        return '';
+        },
         toggleImage(index) {
             this.items[index].isSecondImageVisible = !this.items[index].isSecondImageVisible;
         },
         toggleImageAndShowPopup(index, dataNum) {
             this.toggleImage(index);
-            if (dataNum === '11') {  // 최종 평가 버튼 클릭 시
-                this.searchOpen2 = true;  // 모달창 열기
+            if (dataNum === '11') {
+                this.searchOpen2 = true;
             }
+        },
+        ShowPopup() {
+            this.searchOpen2 = true;  // 촬영 여부 묻는 모달창 열기
         },
         updateContent(index) {
             this.selectedIndex = index;
@@ -401,12 +430,14 @@
                     console.log("error>>>>>>>>", error);
                 });
         },
-        closeModal() {  //웹캠 팝업 닫기
+        closeModal() {  //웹캠 및 모든 팝업 닫기
             // this.showModal = false;
+            this.searchOpen = false;
             this.searchOpen2 = false;
             this.showCameraModal = false;
             this.photoTaken = false;
             this.photo = null;
+            this.showPhotoModal = false;
 
             //스트림 종료
             if (this.stream) {
@@ -733,16 +764,34 @@
                 });
             };
         },
+        getNonNullColumn(item) {
+            if (item.prblm_id !== null) return '문제';
+            if (item.wd_book_id !== null) return '단어장';
+            if (item.text_id !== null) return '지문';
+            if (item.eval_id !== null) return '평가';
+            return ''; 
+        },
+        showConfirm(type) {
+            let message = '';
+            if (type === 'cancel') {
+                message = '삭제하시겠습니까?';
+            } else if (type === 'reset') {
+                message = '초기화하시겠습니까?';
+            } else if (type === 'save') {
+                message = '등록하시겠습니까?';
+            }
 
-
+            if (confirm(message)) {
+                this.goBack();
+            }
+        },
     },
     components: {
         SvgIcon,
     },
     mounted() {
         console.log('main mounted');
-
-
+        this.fetchRoadmapData();
     },
     computed() {
 
@@ -841,4 +890,8 @@
 .toolbar input {
     margin: 5px;
 }
+
+.rabbit-end {
+    cursor: pointer;
+}
 </style>
(No newline at end of file)
client/views/pages/main/MyPage.vue
--- client/views/pages/main/MyPage.vue
+++ client/views/pages/main/MyPage.vue
@@ -98,7 +98,7 @@
                     </div>
                     <div class="box " style="gap: 10px;">
                         <div><img src="../../../resources/img/img196_12p.png" alt=""></div>
-                        <P class="title2 mt10">현재 30명 중 <em class="blue">2등</em>입니다.</P>
+                        <P class="title2 mt10">현재 {{ stdCount }}명 중 <em class="blue">{{ rank }}등</em>입니다.</P>
                     </div>
                 </div>
                 <div class="textbook book-navy">
@@ -121,11 +121,15 @@
 import { mdiMagnify,  } from '@mdi/js';
 import { mdilArrowRight } from '@mdi/light-js';
 import ProgressBar from '../../component/ProgressBar.vue';
+import axios from "axios";
 
 
 export default {
     data() {
         return {
+            stdCount : 0,
+            rank: 0,
+
             mdiMagnify: mdiMagnify,
             mdilArrowRight: mdilArrowRight,
             timer: "00:00",
@@ -133,6 +137,51 @@
         }
     },
     methods: {
+        classStdCount: function () {
+            const vm = this;
+            axios({
+                url: "/userclass/classStdCount.json",
+                method: "post",
+                headers:{
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    sclsId:"1"
+                }
+            })
+            .then(function (response) {
+                console.log("classStdCount - response : ", response.data);
+                vm.stdCount = response.data;
+            })
+            .catch(function (error) {
+                console.log("classStdCount - error : ", error);
+                alert("반 학생 수 조회에 오류가 발생했습니다.");
+            });
+        },
+
+        photoRankByLikeData: function () {
+            const vm = this;
+            axios({
+                url: "/photo/photoRankByLikeData.json",
+                method: "post",
+                headers:{
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    sclsId:"1",
+                    stdId:"1"
+                }
+            })
+            .then(function (response) {
+                console.log("Rank - response : ", response.data);
+                vm.rank = response.data;
+            })
+            .catch(function (error) {
+                console.log("Rank - error : ", error);
+                alert("학생 사진 랭킹 조회에 오류가 발생했습니다.");
+            });
+        },
+
         goToPage(page) {
             this.$router.push({ name: page });
         },
@@ -168,6 +217,8 @@
     },
     mounted() {
         console.log('Main2 mounted');
+        this.classStdCount();
+        this.photoRankByLikeData();
     }
 }
 </script>
client/views/pages/main/MyPlan.vue
--- client/views/pages/main/MyPlan.vue
+++ client/views/pages/main/MyPlan.vue
@@ -1,31 +1,72 @@
 <template>
-    <div class="myplan">
-        <div class="title-box flex justify-between mb40">
-            <p class="title">오늘 공부할 내용을 확인해봅시다.</p>
-            <!-- <select name="" id="">
-                <option value="">A반</option>
-            </select> -->
-        </div>
-        <div class="wrap" style="border-radius: 0; min-height: 197px;">
-           <p class="title1"> 오늘 학습할 내용이 없습니다.</p>
-        </div>
-        <div class="yellow-box mt30">
-            <div class="title-box flex justify-between align-center">
-                <div >
-                    <p class="title">오늘 공부를 계획해봅시다.</p>
-                    <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p>
+
+    <div class="main">
+        <div class="myplan">
+            <div class="title-box flex justify-between mb40">
+                <p class="title">오늘 공부할 내용을 확인해봅시다.</p>
+            </div>
+            <div class="wrap" style="border-radius: 0; min-height: 197px;"> 
+                <p class="title1" v-if="!schedules || schedules.length === 0"> 오늘 학습할 내용이 없습니다.</p>
+ 
+                <div class="flex-column" style="gap: 20px;" v-else v-for="(schedule, index) in schedules" :key="index">
+                    <div class="flex justify-between align-center" style="gap: 70px;">
+                        <div><img src="../../../resources/img/img217_22s.png" alt=""></div>
+                        <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" style="width: 100%;">
+                            <div class="text-lf flex justify-between align-center">
+                                <div>
+                                    <p class="title2">grade 3</p>
+                                    <div class="flex align-center mb10" style="gap: 10px;">
+                                        <p class="title2"><em class="gray-bd">{{ schedule.schdl_unit }}교시</em></p>
+                                        <p class="title1">{{ schedule.schedule_time }}</p> 
+                                    </div>
+                                    <div class="title-box mb10">
+                                        <span class="title">{{schedule.unit_nm}}</span> 
+                                    </div>
+                                    <p class="title2">{{ schedule.book_nm }}</p>
+                                    <!-- <p class="title2">wirte a</p> -->
+                                </div>
+                                <div><img src="../../../resources/img/img214_19s.png" alt=""></div>
+                            </div>
+                        </div>
+                    </div>
                 </div>
-                <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')">
-                    바로가기
-                </button>
+            </div>
+
+            <div class="yellow-box mt30">
+                <div class="title-box flex justify-between align-center">
+                    <div>
+                        <p class="title">오늘 공부를 계획해봅시다.</p>
+                        <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p>
+                    </div>
+                    <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')">
+                        바로가기
+                    </button>
+                </div>
+            </div>
+            <div class="wrap mt30">
+                <p class="title1 mb20"> AI 맞춤형 학습 코스는 어떨까요?</p>
+                <div class="imgGroup flex justify-between">
+                    <div class="text-lf" @click="goToPage('AIDashboard')">
+                        <img src="../../../resources/img/img215_22s.png" alt="">
+                        <p class="title2 mt10">추천 학습 단원</p>
+                    </div>
+                    <div class="text-lf">
+                        <img src="../../../resources/img/img215_22s.png" alt="">
+                        <p class="title2 mt10">추천 학습 단원</p>
+                    </div>
+                    <div class="text-lf">
+                        <img src="../../../resources/img/img215_22s.png" alt="">
+                        <p class="title2 mt10">추천 학습 단원</p>
+                    </div>
+                </div>
             </div>
         </div>
     </div>
 
-    
 </template>
 
 <script>
+import axios from "axios";
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiMagnify, mdiWindowClose } from '@mdi/js';
 
@@ -37,6 +78,16 @@
             mdiWindowClose: mdiWindowClose,
             showModal: false,
             searchOpen: false,
+            schedules: [],
+            stdId: "2",
+            timeList: [
+                { label: "1교시", time: "08:00 ~ 09:00", value: "1" },
+                { label: "2교시", time: "09:00 ~ 10:00", value: "2" },
+                { label: "3교시", time: "10:00 ~ 11:00", value: "3" },
+                { label: "4교시", time: "11:00 ~ 12:00", value: "4" },
+                { label: "5교시", time: "13:00 ~ 14:00", value: "5" },
+                { label: "6교시", time: "14:00 ~ 15:00", value: "6" },
+            ],
         }
     },
     methods: {
@@ -66,8 +117,27 @@
         closeBtn() {
             this.searchOpen = false;
 
-        },
-
+        }, fetchSchedules() {
+            axios.post("/schedule/selectSchedule.json", { stdId: this.stdId }, {
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                }
+            })
+                .then(response => {
+                    this.schedules = response.data.map(schedule => { 
+                        const matchingTime = this.timeList.find(time => time.value === schedule.schdl_unit);
+                        return {
+                            ...schedule,
+                            schedule_time: matchingTime ? matchingTime.time : "시간 정보 없음"
+                        };
+                    });
+                    console.log(this.schedules);
+                })
+                .catch(error => {
+                    console.error("fetchUnits - error:", error);
+                    alert("단원 목록을 불러오는 중 오류가 발생했습니다.");
+                });
+        }
     },
     watch: {
 
@@ -80,6 +150,7 @@
     },
     mounted() {
         console.log('Main2 mounted');
+        this.fetchSchedules();
     }
 }
 </script>
(No newline at end of file)
client/views/pages/main/MyPlan2.vue
--- client/views/pages/main/MyPlan2.vue
+++ client/views/pages/main/MyPlan2.vue
@@ -2,19 +2,13 @@
     <div class=" main">
         <div class="myplan">
             <div class="title-box flex justify-between mb40">
-            <p class="title">오늘 공부를 계획해봅시다.</p>
-            <!-- <select name="" id="">
-                <option value="">A반</option>
-            </select> -->
-        </div>
+                <p class="title">오늘 공부를 계획해봅시다.</p>
+            </div>
             <div class="wrap">
                 <p class="title1"> 학습 교재를 선택합시다.</p>
                 <div class="search-wrap flex justify-end mb20 mt20">
-                    <!-- <select name="" id="" class="mr10 data-wrap">
-                       <option value="">전체</option>
-                   </select> -->
-                    <input class="data-wrap" type="text" placeholder="검색하세요.">
-                    <button type="button" title="위원회 검색">
+                    <input class="data-wrap" type="text" placeholder="검색하세요." v-model="keyword">
+                    <button type="button" title="위원회 검색" @click="searchBook">
                         <img src="../../../resources/img/look_t.png" alt="">
                     </button>
                 </div>
@@ -26,93 +20,53 @@
                             <col style="width: 70%;">
                             <col style="width: 15%;">
                         </colgroup>
-                        <tr>
-                            <td><input type="checkbox" class="ui-checkbox mr10"></td>
+                        <tr v-for="(book, index) in books" :key="index">
+                            <td><input type="checkbox" class="ui-checkbox mr10" v-model="book.isSelected"></td>
                             <td><img src="../../../resources/img/img214_19s.png" alt=""></td>
                             <td class="text-lf">
-                                <p class="title1">the best</p>
-                                <p class="title2">wirte a</p>
+                                <p class="title1">{{ book.book_nm }}</p>
+                                <p class="title2">grade 3</p>
                             </td>
-                            <td> <button type="button" title="확인" class="new-btn" @click="buttonSearch">
-                                    시간입력
-                                </button></td>
-                        </tr>
-                        <tr>
-                            <td><input type="checkbox" class="ui-checkbox mr10"></td>
-                            <td><img src="../../../resources/img/img214_19s.png" alt=""></td>
-                            <td class="text-lf">
-                                <p class="title1">the best</p>
-                                <p class="title2">wirte a</p>
-                            </td>
-                            <td> <button type="button" title="확인" class="new-btn" @click="buttonSearch">
+                            <td> <button type="button" title="확인" class="new-btn"
+                                    @click="buttonSearch(book.book_nm, book.book_id)">
                                     시간입력
                                 </button></td>
                         </tr>
                     </table>
                     <div class="flex justify-end">
-                        <button type="button" title="선택하기" class="yellow-btn mt30">
+                        <button type="button" title="선택하기" class="yellow-btn mt30" @click="checkSchedule">
                             선택하기
                         </button>
                     </div>
                 </div>
             </div>
-            <div class="wrap mt30">
-                <p class="title1 mb20"> AI 맞춤형 학습 코스는 어떨까요?</p>
-                <div class="imgGroup flex justify-between">
-                    <div class="text-lf">
-                        <img src="../../../resources/img/img215_22s.png" alt="">
-                        <p class="title2 mt10">추천 학습 단원</p>
-                    </div>
-                    <div class="text-lf">
-                        <img src="../../../resources/img/img215_22s.png" alt="">
-                        <p class="title2 mt10">추천 학습 단원</p>
-                    </div>
-                    <div class="text-lf">
-                        <img src="../../../resources/img/img215_22s.png" alt="">
-                        <p class="title2 mt10">추천 학습 단원</p>
-                    </div>
-                </div>
-            </div>
+
             <div class="wrap mt30">
                 <p class="title1 mb20">학습일정을 확인해봅시다. </p>
-                <div class="flex-column" style="gap: 20px;">
+                <div class="flex-column" style="gap: 20px;" v-for="(book, index) in DataArray" :key="index">
                     <div class=" flex justify-between align-center " style="gap: 70px;">
                         <div><img src="../../../resources/img/img217_22s.png" alt=""></div>
-                        <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" @click="toggleClicked" style="width: 100%;">
+                        <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }" style="width: 100%;">
                             <div class="text-lf flex justify-between align-center ">
-                               <div>
+                                <div>
                                     <div class="flex align-center mb10" style="gap: 10px;">
-                                        <p class="title2"><em class="gray-bd">1교시</em></p>
-                                        <p class="title1">9:00</p>
-                                        <p class="title1">~</p>
-                                        <p class="title1">10:00</p>
+                                        <p class="title2"><em class="gray-bd">{{ book.scheduleUnit }}교시</em></p>
+                                        <p class="title1">{{ book.scheduleTime }}</p>
                                     </div>
-                                    <div class="title-box  mb10"> <span class="title">the best</span></div>
+                                    <div class="title-box  mb10"> <span class="title">{{ book.bookNm }}</span></div>
                                     <p class="title2">wirte a</p>
-                               </div>
-                               <div class=""> <img src="../../../resources/img/img214_19s.png" alt=""></div>
+                                </div>
+                                <div class=""> <img src="../../../resources/img/img214_19s.png" alt=""></div>
                             </div>
                         </div>
                     </div>
-                    
                 </div>
                 <div class="flex justify-end">
-                        <button type="button" title="선택하기" class="yellow-btn mt30">
-                            선택하기
-                        </button>
-                    </div>
-            </div>
-            <!-- <div class="yellow-box mt30">
-                <div class="title-box flex justify-between align-center">
-                    <div>
-                        <p class="title">학습 일정 변경</p>
-                        <p class="title1 mt20"> 스스로 학습 일정을 바꿔볼까요?</p>
-                    </div>
-                    <button type="button" title="바로가기" class="yellow-btn" @click="goToPage('MyPlan2')">
-                    바로가기
-                </button>
+                    <button type="button" title="저장하기" class="yellow-btn mt30" @click="insertSchedule">
+                        저장하기
+                    </button>
                 </div>
-            </div> -->
+            </div>
             <div class="popup-wrap" v-show="searchOpen">
                 <div class="popup-box ">
                     <div class="flex mb10  justify-between">
@@ -121,74 +75,25 @@
                             <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
                         </button>
                     </div>
-                    <article class=" mb20 flex-column " style="gap: 20px;">
-                        <div class="flex justify-between bg-gray">
-                            <div class="flex align-center">
-                                <div><input type="checkbox" class="ui-checkbox mr20"></div>
-                                <div class="text-lf">
-                                    <p class="title1">2교시</p>
+                    <article class="mb20 flex-column" style="gap: 20px;">
+                        <div v-for="(item, index) in timeList" :key="item.value" class="flex justify-between bg-gray">
+                            <div class="flex items-center">
+                                <div>
+                                    <input type="checkbox" class="ui-checkbox mr20" v-model="checkedItems"
+                                        :value="item" />
+                                </div>
+                                <div class="text-lg">
+                                    <p class="title1">{{ item.label }}</p>
                                 </div>
                             </div>
-                            <div>
-                                <div class="flex align-center " style="gap: 10px;"><img
-                                        src="../../../resources/img/img215_20s.png" alt="">
-                                    <p class="title1">9:00</p>
-                                    <p class="title1">~</p>
-                                    <p class="title1">10:00</p>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="flex justify-between bg-gray">
-                            <div class="flex align-center">
-                                <div><input type="checkbox" class="ui-checkbox mr20"></div>
-                                <div class="text-lf">
-                                    <p class="title1">2교시</p>
-                                </div>
-                            </div>
-                            <div>
-                                <div class="flex align-center " style="gap: 10px;"><img
-                                        src="../../../resources/img/img215_20s.png" alt="">
-                                    <p class="title1">9:00</p>
-                                    <p class="title1">~</p>
-                                    <p class="title1">10:00</p>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="flex justify-between bg-gray">
-                            <div class="flex align-center">
-                                <div><input type="checkbox" class="ui-checkbox mr20"></div>
-                                <div class="text-lf">
-                                    <p class="title1">2교시</p>
-                                </div>
-                            </div>
-                            <div>
-                                <div class="flex align-center " style="gap: 10px;"><img
-                                        src="../../../resources/img/img215_20s.png" alt="">
-                                    <p class="title1">9:00</p>
-                                    <p class="title1">~</p>
-                                    <p class="title1">10:00</p>
-                                </div>
-                            </div>
-                        </div>
-                        <div class="flex justify-between bg-gray">
-                            <div class="flex align-center">
-                                <div><input type="checkbox" class="ui-checkbox mr20"></div>
-                                <div class="text-lf">
-                                    <p class="title1">2교시</p>
-                                </div>
-                            </div>
-                            <div>
-                                <div class="flex align-center " style="gap: 10px;"><img
-                                        src="../../../resources/img/img215_20s.png" alt="">
-                                    <p class="title1">9:00</p>
-                                    <p class="title1">~</p>
-                                    <p class="title1">10:00</p>
-                                </div>
+                            <div class="flex align-center" style="gap: 10px;">
+                                <img src="../../../resources/img/img215_20s.png" alt="" />
+                                <p class="title1">{{ item.time }}</p>
                             </div>
                         </div>
                     </article>
                     <div class="flex justify-center mt20">
-                        <button type="button" title="선택" class="new-btn">
+                        <button type="button" title="선택" class="new-btn" @click="timePick">
                             선택
                         </button>
                     </div>
@@ -200,9 +105,9 @@
 </template>
 
 <script>
+import axios from "axios";
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiMagnify, mdiWindowClose } from '@mdi/js';
-
 
 export default {
     data() {
@@ -212,12 +117,28 @@
             mdiWindowClose: mdiWindowClose,
             showModal: false,
             searchOpen: false,
+            books: [],
+            timeList: [
+                { label: "1교시", time: "08:00 ~ 09:00", value: "1" },
+                { label: "2교시", time: "09:00 ~ 10:00", value: "2" },
+                { label: "3교시", time: "10:00 ~ 11:00", value: "3" },
+                { label: "4교시", time: "11:00 ~ 12:00", value: "4" },
+                { label: "5교시", time: "13:00 ~ 14:00", value: "5" },
+                { label: "6교시", time: "14:00 ~ 15:00", value: "6" },
+            ],
+            checkedItems: [],
+            newDataArray: [],
+            DataArray: [],
+            nowName: "",
+            nowID: "",
+            keyword: "",
+            unitList: []
         }
     },
     methods: {
         toggleClicked() {
-      this.isClicked = !this.isClicked;
-    },
+            this.isClicked = !this.isClicked;
+        },
         goToPage(page) {
             this.$router.push({ name: page });
         },
@@ -238,32 +159,130 @@
         closeModal() {
             this.showModal = false;
         },
-        buttonSearch() {
+        buttonSearch(book_nm, book_id) {
+            this.nowID = book_id
+            this.nowName = book_nm
             this.searchOpen = true;
         },
         closeBtn() {
             this.searchOpen = false;
-
         },
+        timePick() {
+            const selectedBooks = this.books.filter(book => book.isSelected);
+            if (selectedBooks.length <= 0) {
+                alert('교재를 선택해 주세요.');
+            } else {
+                if (this.checkedItems.length === 1) {
+                    if (this.unitList.find(item => item === this.checkedItems[0])) {
+                        alert('이미 지정된 시간입니다.');
+                    } else {
+                        this.addToNewDataArray(this.checkedItems[0]);
+                        this.searchOpen = false;
+                        this.nowID = ""
+                        this.nowName = ""
+                        this.unitList.push(this.checkedItems[0])
+                        this.checkedItems = []
+                    }
+                } else if (this.checkedItems.length > 1) {
+                    alert('시간은 하나만 선택할 수 있습니다.');
+                } else if (this.checkedItems.length <= 0) {
+                    alert('시간을 선택해 주세요.');
+                }
+            }
+        },
+        addToNewDataArray(selectedTime) {
+            if (!this.newDataArray) {
+                this.newDataArray = [];
+            }
+            const newEntry = {
+                bookNm: this.nowName,
+                scheduleTime: selectedTime.time,
+                scheduleUnit: selectedTime.value,
+                bookId: this.nowID,
+                stdId: "2"
+            };
+            this.newDataArray.push(newEntry);
+        },
+        async insertSchedule() {
 
+            this.newDataArray = this.DataArray.map(item => ({
+                scheduleUnit: item.scheduleUnit,
+                bookId: item.bookId,
+                stdId: item.stdId
+            }));
+            try {
+                for (let data of this.newDataArray) {
+                    await axios.post('/schedule/insertSchedule.json', data);
+                }
+                alert('학습 일정이 등록됐어요!');
+                this.goToPage('MyPlan')
+            } catch (error) {
+                console.error('데이터 제출 중 오류 발생:', error);
+                alert('데이터 제출 중 오류가 발생했습니다.');
+            }
+        }, checkSchedule() {
+            const sortedArray = this.newDataArray.slice().sort((a, b) => {
+                return a.scheduleUnit - b.scheduleUnit;
+            });
+
+            this.DataArray = sortedArray;
+        },
+        fetchBooks() {
+            axios({
+                url: "/book/findAll.json",
+                method: "post",
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+            })
+                .then(response => {
+                    this.books = response.data.map(book => ({
+                        ...book,
+                        isSelected: false
+                    }));
+                })
+                .catch(error => {
+                    console.error("fetchBooks - error: ", error);
+                    alert("교재 목록을 불러오는 중 오류가 발생했습니다.");
+                });
+        },
+        searchBook() {
+            this.books = ""
+            axios.post("/book/search.json", { book_nm: this.keyword }, {
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                }
+            })
+                .then(response => {
+                    this.books = response.data.list.map(book => ({
+                        ...book,
+                        isSelected: false
+                    }));
+                })
+                .catch(error => {
+                    console.error("fetchUnits - error:", error);
+                    alert("단원 목록을 불러오는 중 오류가 발생했습니다.");
+                });
+        }
     },
     watch: {
-
+        // Watchers for reactivity (if needed)
     },
     computed: {
-
+        // Computed properties (if needed)
     },
     components: {
         SvgIcon
     },
     mounted() {
         console.log('Main2 mounted');
+        this.fetchBooks();
     }
 }
 </script>
+
 <style scoped>
 .popup-box {
     width: 750px;
-
 }
 </style>
(No newline at end of file)
 
client/views/pages/parents/Barchart.vue (added)
+++ client/views/pages/parents/Barchart.vue
@@ -0,0 +1,204 @@
+<template>
+    <div ref="Barchart" style="width: 500px; height: 500px;"></div>
+  </template>
+  
+  <script>
+  import * as am5 from "@amcharts/amcharts5";
+  import * as am5xy from "@amcharts/amcharts5/xy";
+  import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+  
+  export default {
+    name: "Barchart",
+    mounted() {
+      this.createChart();
+    },
+    methods: {
+      createChart() {
+        // Initialize root
+        const root = am5.Root.new(this.$refs.Barchart);
+  
+        // Apply themes
+        const myTheme = am5.Theme.new(root);
+        myTheme.rule("Grid", ["base"]).setAll({
+          strokeOpacity: 0.1,
+        });
+        root.setThemes([
+          am5themes_Animated.new(root),
+          myTheme,
+        ]);
+  
+        // Create chart
+        let chart = root.container.children.push(
+  am5xy.XYChart.new(root, {
+    panX: false,
+    panY: false,
+    wheelX: "none",
+    wheelY: "none",
+    paddingLeft: 0
+  })
+);
+
+
+// Create axes
+// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
+let yRenderer = am5xy.AxisRendererY.new(root, {
+  minGridDistance: 30,
+  minorGridEnabled: true
+});
+yRenderer.grid.template.set("location", 1);
+
+let yAxis = chart.yAxes.push(
+  am5xy.CategoryAxis.new(root, {
+    maxDeviation: 0,
+    categoryField: "country",
+    renderer: yRenderer
+  })
+);
+
+let xAxis = chart.xAxes.push(
+  am5xy.ValueAxis.new(root, {
+    maxDeviation: 0,
+    min: 0,
+    renderer: am5xy.AxisRendererX.new(root, {
+      visible: true,
+      strokeOpacity: 0.1,
+      minGridDistance: 80
+    })
+  })
+);
+
+
+// Create series
+// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
+let series = chart.series.push(
+  am5xy.ColumnSeries.new(root, {
+    name: "Series 1",
+    xAxis: xAxis,
+    yAxis: yAxis,
+    valueXField: "value",
+    sequencedInterpolation: true,
+    categoryYField: "country"
+  })
+);
+
+let columnTemplate = series.columns.template;
+
+columnTemplate.setAll({
+  draggable: true,
+  cursorOverStyle: "pointer",
+  tooltipText: "drag to rearrange",
+  cornerRadiusBR: 10,
+  cornerRadiusTR: 10,
+  strokeOpacity: 0
+});
+columnTemplate.adapters.add("fill", (fill, target) => {
+  return chart.get("colors").getIndex(series.columns.indexOf(target));
+});
+
+columnTemplate.adapters.add("stroke", (stroke, target) => {
+  return chart.get("colors").getIndex(series.columns.indexOf(target));
+});
+
+columnTemplate.events.on("dragstop", () => {
+  sortCategoryAxis();
+});
+
+// Get series item by category
+function getSeriesItem(category) {
+  for (var i = 0; i < series.dataItems.length; i++) {
+    let dataItem = series.dataItems[i];
+    if (dataItem.get("categoryY") == category) {
+      return dataItem;
+    }
+  }
+}
+
+
+// Axis sorting
+function sortCategoryAxis() {
+  // Sort by value
+  series.dataItems.sort(function (x, y) {
+    return y.get("graphics").y() - x.get("graphics").y();
+  });
+
+  let easing = am5.ease.out(am5.ease.cubic);
+
+  // Go through each axis item
+  am5.array.each(yAxis.dataItems, function (dataItem) {
+    // get corresponding series item
+    let seriesDataItem = getSeriesItem(dataItem.get("category"));
+
+    if (seriesDataItem) {
+      // get index of series data item
+      let index = series.dataItems.indexOf(seriesDataItem);
+
+      let column = seriesDataItem.get("graphics");
+
+      // position after sorting
+      let fy =
+        yRenderer.positionToCoordinate(yAxis.indexToPosition(index)) -
+        column.height() / 2;
+
+      // set index to be the same as series data item index
+      if (index != dataItem.get("index")) {
+        dataItem.set("index", index);
+
+        // current position
+        let x = column.x();
+        let y = column.y();
+
+        column.set("dy", -(fy - y));
+        column.set("dx", x);
+
+        column.animate({ key: "dy", to: 0, duration: 600, easing: easing });
+        column.animate({ key: "dx", to: 0, duration: 600, easing: easing });
+      } else {
+        column.animate({ key: "y", to: fy, duration: 600, easing: easing });
+        column.animate({ key: "x", to: 0, duration: 600, easing: easing });
+      }
+    }
+  });
+
+  // Sort axis items by index.
+  // This changes the order instantly, but as dx and dy is set and animated,
+  // they keep in the same places and then animate to true positions.
+  yAxis.dataItems.sort(function (x, y) {
+    return x.get("index") - y.get("index");
+  });
+}
+
+// Set data
+let data = [{
+  country: "USA",
+  value: 2025
+}, {
+  country: "China",
+  value: 1882
+}, {
+  country: "Japan",
+  value: 1809
+}, {
+  country: "Germany",
+  value: 1322
+}, {
+  country: "UK",
+  value: 1122
+}];
+
+yAxis.data.setAll(data);
+series.data.setAll(data);
+
+
+// Make stuff animate on load
+// https://www.amcharts.com/docs/v5/concepts/animations/
+series.appear(1000);
+chart.appear(1000, 100);
+      },
+    },
+  };
+  </script>
+  
+  <style scoped>
+  /* Add necessary styles here */
+  </style>
+  (No newline at end of file)
 
client/views/pages/parents/Bubblechart.vue (added)
+++ client/views/pages/parents/Bubblechart.vue
@@ -0,0 +1,222 @@
+<template>
+    <div ref="Bubblechart" style="width: 500px; height: 500px;"></div>
+  </template>
+  
+  <script>
+  import * as am5 from "@amcharts/amcharts5";
+  import * as am5xy from "@amcharts/amcharts5/xy";
+  import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+  
+  export default {
+    name: "Bubblechart",
+    mounted() {
+      this.createChart();
+    },
+    methods: {
+      createChart() {
+        // Initialize root
+        const root = am5.Root.new(this.$refs.Bubblechart);
+  
+        // Apply themes
+        const myTheme = am5.Theme.new(root);
+        myTheme.rule("Grid", ["base"]).setAll({
+          strokeOpacity: 0.1,
+        });
+        root.setThemes([
+          am5themes_Animated.new(root),
+          myTheme,
+        ]);
+  
+        // Create chart
+        let chart = root.container.children.push(am5xy.XYChart.new(root, {
+  panX: true,
+  panY: true,
+  wheelY: "zoomXY",
+  pinchZoomX:true,
+  pinchZoomY:true
+}));
+
+chart.get("colors").set("step", 2);
+
+// Create axes
+// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
+let xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, {
+  renderer: am5xy.AxisRendererX.new(root, { minGridDistance: 50 }),
+  tooltip: am5.Tooltip.new(root, {})
+}));
+
+let yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
+  renderer: am5xy.AxisRendererY.new(root, {}),
+  tooltip: am5.Tooltip.new(root, {})
+}));
+
+// Create series
+// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
+let series0 = chart.series.push(am5xy.LineSeries.new(root, {
+  calculateAggregates: true,
+  xAxis: xAxis,
+  yAxis: yAxis,
+  valueYField: "y",
+  valueXField: "x",
+  valueField: "value",
+  tooltip: am5.Tooltip.new(root, {
+    labelText: "x: {valueX}, y: {valueY}, value: {value}"
+  })
+}));
+
+
+// Add bullet
+// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets
+let circleTemplate = am5.Template.new({});
+series0.bullets.push(function() {
+  let graphics = am5.Circle.new(root, {
+    fill: series0.get("fill"),
+  }, circleTemplate);
+  return am5.Bullet.new(root, {
+    sprite: graphics
+  });
+});
+
+// Add heat rule
+// https://www.amcharts.com/docs/v5/concepts/settings/heat-rules/
+series0.set("heatRules", [{
+  target: circleTemplate,
+  min: 3,
+  max: 35,
+  dataField: "value",
+  key: "radius"
+}]);
+
+
+// Create second series
+// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
+let series1 = chart.series.push(am5xy.LineSeries.new(root, {
+  calculateAggregates: true,
+  xAxis: xAxis,
+  yAxis: yAxis,
+  valueYField: "y2",
+  valueXField: "x2",
+  valueField: "value",
+  tooltip: am5.Tooltip.new(root, {
+    labelText: "x: {valueX}, y: {valueY}, value: {value}"
+  })
+}));
+
+// Add bullet
+// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets
+let starTemplate = am5.Template.new({});
+series1.bullets.push(function() {
+  let graphics = am5.Star.new(root, {
+    fill: series1.get("fill"),
+    spikes: 8,
+    innerRadius: am5.percent(70),
+  }, starTemplate);
+  return am5.Bullet.new(root, {
+    sprite: graphics
+  });
+});
+
+
+// Add heat rule
+// https://www.amcharts.com/docs/v5/concepts/settings/heat-rules/
+series1.set("heatRules", [{
+  target: starTemplate,
+  min: 3,
+  max: 50,
+  dataField: "value",
+  key: "radius"
+}]);
+
+
+series0.strokes.template.set("strokeOpacity", 0);
+series1.strokes.template.set("strokeOpacity", 0);
+
+// Add cursor
+// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
+chart.set("cursor", am5xy.XYCursor.new(root, {
+  xAxis: xAxis,
+  yAxis: yAxis,
+  snapToSeries: [series0, series1]
+}));
+
+// Add scrollbars
+// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/
+// chart.set("scrollbarX", am5.Scrollbar.new(root, {
+//   orientation: "horizontal"
+// }));
+
+// chart.set("scrollbarY", am5.Scrollbar.new(root, {
+//   orientation: "vertical"
+// }));
+
+
+let data = [{
+  "y": 10,
+  "x": 14,
+  "value": 59,
+  "y2": -5,
+  "x2": -3,
+  "value2": 44
+}, {
+  "y": 5,
+  "x": 3,
+  "value": 50,
+  "y2": -15,
+  "x2": -8,
+  "value2": 12
+}, {
+  "y": -10,
+  "x": 8,
+  "value": 19,
+  "y2": -4,
+  "x2": 6,
+  "value2": 35
+}, {
+  "y": -6,
+  "x": 5,
+  "value": 65,
+  "y2": -5,
+  "x2": -6,
+  "value2": 168
+}, {
+  "y": 15,
+  "x": -4,
+  "value": 92,
+  "y2": -10,
+  "x2": -8,
+  "value2": 102
+}, {
+  "y": 13,
+  "x": 1,
+  "value": 8,
+  "y2": -2,
+  "x2": 0,
+  "value2": 41
+}, {
+  "y": 1,
+  "x": 6,
+  "value": 35,
+  "y2": 0,
+  "x2": -3,
+  "value2": 16
+}]
+
+series0.data.setAll(data);
+series1.data.setAll(data);
+
+
+// Make stuff animate on load
+// https://www.amcharts.com/docs/v5/concepts/animations/
+series0.appear(1000);
+series1.appear(1000);
+
+chart.appear(1000, 100);
+      },
+    },
+  };
+  </script>
+  
+  <style scoped>
+  /* Add necessary styles here */
+  </style>
+  (No newline at end of file)
 
client/views/pages/parents/ColumnLineChart.vue (added)
+++ client/views/pages/parents/ColumnLineChart.vue
@@ -0,0 +1,206 @@
+<template>
+    <div ref="ColumnLineChart" style="width: 500px; height: 500px;"></div>
+  </template>
+  
+  <script>
+  import * as am5 from "@amcharts/amcharts5";
+  import * as am5xy from "@amcharts/amcharts5/xy";
+  import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+  
+  export default {
+    name: "ColumnLineChart",
+    mounted() {
+      this.createChart();
+    },
+    methods: {
+      createChart() {
+        // Initialize root
+        const root = am5.Root.new(this.$refs.ColumnLineChart);
+  
+        // Apply themes
+        const myTheme = am5.Theme.new(root);
+        myTheme.rule("Grid", ["base"]).setAll({
+          strokeOpacity: 0.1,
+        });
+        root.setThemes([
+          am5themes_Animated.new(root),
+          myTheme,
+        ]);
+  
+        // Create chart
+        let chart = root.container.children.push(
+  am5xy.XYChart.new(root, {
+    panX: false,
+    panY: false,
+    wheelX: "panX",
+    wheelY: "zoomX",
+    paddingLeft: 0,
+    layout: root.verticalLayout
+  })
+);
+
+// Add scrollbar
+// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/
+// chart.set(
+//   "scrollbarX",
+//   am5.Scrollbar.new(root, {
+//     orientation: "horizontal"
+//   })
+// );
+
+let data = [
+  {
+    year: "2016",
+    income: 23.5,
+    expenses: 21.1
+  },
+  {
+    year: "2017",
+    income: 26.2,
+    expenses: 30.5
+  },
+  {
+    year: "2018",
+    income: 30.1,
+    expenses: 34.9
+  },
+  {
+    year: "2019",
+    income: 29.5,
+    expenses: 31.1
+  },
+  {
+    year: "2020",
+    income: 30.6,
+    expenses: 28.2,
+    strokeSettings: {
+      stroke: chart.get("colors").getIndex(1),
+      strokeWidth: 3,
+      strokeDasharray: [5, 5]
+    }
+  },
+  {
+    year: "2021",
+    income: 34.1,
+    expenses: 32.9,
+    columnSettings: {
+      strokeWidth: 1,
+      strokeDasharray: [5],
+      fillOpacity: 0.2
+    },
+    info: "(projection)"
+  }
+];
+
+// Create axes
+// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
+let xRenderer = am5xy.AxisRendererX.new(root, {
+  minorGridEnabled: true,
+  minGridDistance: 60
+});
+let xAxis = chart.xAxes.push(
+  am5xy.CategoryAxis.new(root, {
+    categoryField: "year",
+    renderer: xRenderer,
+    tooltip: am5.Tooltip.new(root, {})
+  })
+);
+xRenderer.grid.template.setAll({
+  location: 1
+})
+
+xAxis.data.setAll(data);
+
+let yAxis = chart.yAxes.push(
+  am5xy.ValueAxis.new(root, {
+    min: 0,
+    extraMax: 0.1,
+    renderer: am5xy.AxisRendererY.new(root, {
+      strokeOpacity: 0.1
+    })
+  })
+);
+
+
+// Add series
+// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
+
+let series1 = chart.series.push(
+  am5xy.ColumnSeries.new(root, {
+    name: "Income",
+    xAxis: xAxis,
+    yAxis: yAxis,
+    valueYField: "income",
+    categoryXField: "year",
+    tooltip: am5.Tooltip.new(root, {
+      pointerOrientation: "horizontal",
+      labelText: "{name} in {categoryX}: {valueY} {info}"
+    })
+  })
+);
+
+series1.columns.template.setAll({
+  tooltipY: am5.percent(10),
+  templateField: "columnSettings"
+});
+
+series1.data.setAll(data);
+
+let series2 = chart.series.push(
+  am5xy.LineSeries.new(root, {
+    name: "Expenses",
+    xAxis: xAxis,
+    yAxis: yAxis,
+    valueYField: "expenses",
+    categoryXField: "year",
+    tooltip: am5.Tooltip.new(root, {
+      pointerOrientation: "horizontal",
+      labelText: "{name} in {categoryX}: {valueY} {info}"
+    })
+  })
+);
+
+series2.strokes.template.setAll({
+  strokeWidth: 3,
+  templateField: "strokeSettings"
+});
+
+
+series2.data.setAll(data);
+
+series2.bullets.push(function () {
+  return am5.Bullet.new(root, {
+    sprite: am5.Circle.new(root, {
+      strokeWidth: 3,
+      stroke: series2.get("stroke"),
+      radius: 5,
+      fill: root.interfaceColors.get("background")
+    })
+  });
+});
+
+chart.set("cursor", am5xy.XYCursor.new(root, {}));
+
+// Add legend
+// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
+let legend = chart.children.push(
+  am5.Legend.new(root, {
+    centerX: am5.p50,
+    x: am5.p50
+  })
+);
+legend.data.setAll(chart.series.values);
+
+// Make stuff animate on load
+// https://www.amcharts.com/docs/v5/concepts/animations/
+chart.appear(1000, 100);
+series1.appear();
+      },
+    },
+  };
+  </script>
+  
+  <style scoped>
+  /* Add necessary styles here */
+  </style>
+  (No newline at end of file)
 
client/views/pages/parents/Dounutchart.vue (added)
+++ client/views/pages/parents/Dounutchart.vue
@@ -0,0 +1,81 @@
+<template>
+  <div ref="Dounutchart" style="width: 500px; height: 500px;"></div>
+</template>
+
+<script>
+import * as am5 from "@amcharts/amcharts5";
+import * as am5percent from "@amcharts/amcharts5/percent";
+import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+
+export default {
+  name: "Dounutchart",
+  mounted() {
+    this.createChart();
+  },
+  methods: {
+    createChart() {
+      // Initialize root
+      const root = am5.Root.new(this.$refs.Dounutchart);
+
+      // Apply themes
+      root.setThemes([
+        am5themes_Animated.new(root)
+      ]);
+
+      // Create chart
+      const chart = root.container.children.push(am5percent.PieChart.new(root, {
+        layout: root.verticalLayout,
+        innerRadius: am5.percent(50)  // Adjusted innerRadius for a donut chart
+      }));
+
+      // Create series
+      const series = chart.series.push(am5percent.PieSeries.new(root, {
+        valueField: "value",
+        categoryField: "category",
+        alignLabels: false
+      }));
+
+      // Add static label
+      // const label = chart.plotContainer.children.push(am5.Label.new(root, {
+      //   text: "Total",
+      //   x: am5.p50,
+      //   y: am5.p50,
+      //   centerX: am5.p50,
+      //   centerY: am5.p50,
+      //   fill: am5.color(0x000000),
+      //   fontSize: 20,
+      //   fontWeight: "bold"
+      // }));
+
+      series.labels.template.setAll({
+        textType: "circular",
+        centerX: am5.p50,
+        centerY: am5.p50
+      });
+
+      // Set data
+      series.data.setAll([
+        { value: 10, category: "One" },
+        { value: 9, category: "Two" },
+      ]);
+
+      // Create legend
+      const legend = chart.children.push(am5.Legend.new(root, {
+        centerX: am5.p50,
+        x: am5.p50,
+        marginTop: 15,
+        marginBottom: 15
+      }));
+
+      legend.data.setAll(series.dataItems);
+
+      // Play initial series animation
+      series.appear(1000, 100);
+    }
+  }
+};
+</script>
+
+<style scoped>
+/* Add necessary styles here */
+</style>
client/views/pages/parents/Main_p.vue
--- client/views/pages/parents/Main_p.vue
+++ client/views/pages/parents/Main_p.vue
@@ -1,15 +1,195 @@
 <template>
-    <div>Main.vue</div>
+    <div>
+        <!-- <Side_t></Side_t> -->
+        <div style="padding: 15px 60px 120px 60px ">
+            <div class="flex justify-between align-center">
+                <div class="logo mb25"><img src="../../../resources/img/logo2.png" alt=""></div>
+                <Header></Header>
+            </div>
+            <div class="main-wrap flex justify-between">
+                <div class="gd-2">
+                    <div class=" mb30">
+                        <div>
+                            <img src="../../../resources/img/img16_s.png" alt="">
+                            <div class="mt10" style="width: 100%;">
+                                <p class="name mb10">학생이름</p>
+                                <p class="mb5">xx중학교 3학년 x반</p>
+                                <progress-bar :progress="progress"></progress-bar>
+                                <span @click="increaseProgress">오늘의 공부</span>
+                                <span class="brown ml10">{{ progress }}%</span>
+                            </div>
+                        </div>
+                        <hr>
+                        <p class="title2 mb25">최근 학습 히스토리</p>
+                        <ul class="flex justify-between ml30">
+                            <li>자학사 3학년 2학기</li>
+                            <li>자학사 3학년 2학기</li>
+                        </ul>
+
+                        <hr>
+                        <div class="title-box flex justify-between mb20">
+                            <p class="name">가나다학생 랭킹</p>
+                        </div>
+                        <div class="mypage mb30">
+                            <div class=" flex-column " style="gap: 20px;">
+                                <div class="textbook book-red">
+                                    <div class="text ">
+                                        <p class="title1" style="color: #fff;">포토북 랭킹</p>
+                                    </div>
+                                    <div class="box">
+                                        <P class="title2 mt10">현재 30명 중 <em class="red">2등</em>입니다.</P>
+                                    </div>
+                                </div>
+                                <div class="textbook ">
+                                    <div class="text ">
+                                        <p class="title1" style="color: #fff;">포토북 랭킹</p>
+                                    </div>
+                                    <div class="box">
+                                        <P class="title2 mt10">현재 30명 중 <em class="yellow">2등</em>입니다.</P>
+                                    </div>
+                                </div>
+                                <div class="textbook book-blue">
+                                    <div class="text ">
+                                        <p class="title1" style="color: #fff;">포토북 랭킹</p>
+                                    </div>
+                                    <div class="box">
+                                        <P class="title2 mt10">현재 30명 중 <em class="blue">2등</em>입니다.</P>
+                                    </div>
+                                </div>
+                                <div class="textbook book-navy">
+                                    <div class="text ">
+                                        <p class="title1" style="color: #fff;">포토북 랭킹</p>
+                                    </div>
+                                    <div class="box">
+                                        <P class="title2 mt10">현재 30명 중 <em class="navy">2등</em>입니다.</P>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <hr>
+                        <div>
+                            <div class="title-box flex justify-between mb20">
+                                <p class="title">사진첩</p>
+                            </div>
+                            <div class="photobook">
+                                <div class="flex justify-between">
+                                    <div class="box" style="gap: 5px;">
+                                        <div><img src="../../../resources/img/img198_12p.png" alt=""></div>
+                                    </div>
+                                    <div class="text mt10">
+                                        <p class="title1 mb10">나의 사진첩</p>
+                                        <button @click="selectedTab = 'tab4'; goToPage('PhotoBook')" type="button"
+                                            title="글쓰기" class="new-btn">
+                                            바로가기
+                                        </button>
+
+                                    </div>
+                                </div>
+
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="gd-9">
+                    <div class="title-box flex justify-between mb40">
+                        <p class="title">전체 진행률</p>
+                    </div>
+                    <div class="flex">
+                        <div class="wrap">
+                            <p class="name">학습 현황</p>
+                            <div><Dounutchart/></div>
+                            <div class="textbox">
+                                <p class="title2">오늘의 학습현황</p>
+                                <p class="name">40%</p>
+                            </div>
+                            <p class="title2">학습시간</p>
+                            <p class="name">학습시간 0시간</p>
+                        </div>
+                        <div class="wrap">
+                            <p class="name">이해/표현 점수</p>
+                            <div>
+                                <ColumnLineChart/>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="wrap">
+                        <p class="name">교재별 진행률</p>
+                        <div>
+                            <StackedBarChart />
+                        </div>
+                    </div>
+                    <div class="flex">
+                        <div class="wrap">
+                            <p class="name">오늘의 학습 일정</p>
+                            <div class="flex-column" style="gap: 20px;">
+                                <div class=" flex justify-between align-center " style="gap: 70px;">
+                                    <div><img src="../../../resources/img/img217_22s.png" alt=""></div>
+                                    <div class="wrap cs-pt" :class="{ 'cs-pt-clicked': isClicked }"
+                                        @click="toggleClicked" style="width: 100%;">
+                                        <div class="text-lf flex justify-between align-center ">
+                                            <div>
+                                                <div class="flex align-center mb10" style="gap: 10px;">
+                                                    <p class="title2"><em class="gray-bd">1교시</em></p>
+                                                    <p class="title1">9:00</p>
+                                                    <p class="title1">~</p>
+                                                    <p class="title1">10:00</p>
+                                                </div>
+                                                <div class="title-box  mb10"> <span class="title">the best</span></div>
+                                                <p class="title2">wirte a</p>
+                                            </div>
+                                            <div class=""> <img src="../../../resources/img/img214_19s.png" alt="">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                            </div>
+                        </div>
+                        <div class="flex-column">
+                            <div class="wrap">
+                                <p class="name">교재별 오답률</p>
+                                <Barchart />
+                            </div>
+
+                            <div class="wrap">
+                                <p class="name">LC/RC 세부 점수</p>
+                                <Bubblechart />
+                            </div>
+
+                        </div>
+
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
 </template>
 
 <script>
+import Header from '../../layout/Header.vue';
+import Menu from '../../layout/Menu.vue';
+import Side_t from '../../layout/Side_t.vue';
+import ProgressBar from '../../component/ProgressBar.vue';
+import StackedBarChart from './StackedBarChart.vue';
+import Barchart from './Barchart.vue';
+import Bubblechart from './Bubblechart.vue';
+import Dounutchart from './Dounutchart.vue';
+import ColumnLineChart from './ColumnLineChart.vue';
 
 export default {
-    data () {
+    data() {
         return {
+            progress: 20
         }
     },
     methods: {
+        increaseProgress() {
+            if (this.progress < 100) {
+                this.progress += 10;
+            }
+        },
+       
 
     },
     watch: {
@@ -19,9 +199,27 @@
 
     },
     components: {
+        Header: Header,
+        Menu: Menu,
+        // Footer:Footer,
+        Side_t: Side_t,
+        ProgressBar,
+        StackedBarChart: StackedBarChart,
+        Barchart: Barchart,
+        Bubblechart: Bubblechart,
+        Dounutchart: Dounutchart,
+        ColumnLineChart: ColumnLineChart,
+
     },
     mounted() {
-        console.log('main mounted');
     }
 }
-</script>
(No newline at end of file)
+</script>
+<style scoped>
+.main-wrap {
+    margin-top: 20px;
+    position: static;
+    width: 100%;
+    height: 100%;
+}
+</style>
(No newline at end of file)
 
client/views/pages/parents/StackedBarChart.vue (added)
+++ client/views/pages/parents/StackedBarChart.vue
@@ -0,0 +1,129 @@
+<template>
+    <div ref="StackedBarChart" style=" height: 500px;"></div>
+  </template>
+  
+  <script>
+  import * as am5 from "@amcharts/amcharts5";
+  import * as am5xy from "@amcharts/amcharts5/xy";
+  import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+  
+  export default {
+    name: "StackedBarChart",
+    mounted() {
+      this.createChart();
+    },
+    methods: {
+      createChart() {
+        // Initialize root
+        const root = am5.Root.new(this.$refs.StackedBarChart);
+  
+        // Apply themes
+        const myTheme = am5.Theme.new(root);
+        myTheme.rule("Grid", ["base"]).setAll({
+          strokeOpacity: 0.1,
+        });
+        root.setThemes([
+          am5themes_Animated.new(root),
+          myTheme,
+        ]);
+  
+        // Create chart
+        const chart = root.container.children.push(
+          am5xy.XYChart.new(root, {
+            panX: false,
+            panY: false,
+            wheelX: "panY",
+            wheelY: "zoomY",
+            paddingLeft: 0,
+            layout: root.verticalLayout,
+          })
+        );
+  
+        // Define data
+        const data = [
+          { year: "2021", europe: 2.5, namerica: 2.5, asia: 2.1, lamerica: 1, meast: 0.8, africa: 0.4 },
+        ];
+  
+        // Create Y Axis
+        const yRenderer = am5xy.AxisRendererY.new(root, {});
+        const yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
+          categoryField: "year",
+          renderer: yRenderer,
+          tooltip: am5.Tooltip.new(root, {}),
+        }));
+        yRenderer.grid.template.setAll({
+          location: 1,
+        });
+        yAxis.data.setAll(data);
+  
+        // Create X Axis
+        const xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, {
+          min: 0,
+          maxPrecision: 0,
+          renderer: am5xy.AxisRendererX.new(root, {
+            minGridDistance: 40,
+            strokeOpacity: 0.1,
+          }),
+        }));
+  
+        // Create legend
+        const legend = chart.children.push(am5.Legend.new(root, {
+          centerX: am5.p50,
+          x: am5.p50,
+        }));
+  
+        // Function to create series
+        const createSeries = (name, fieldName) => {
+          const series = chart.series.push(am5xy.ColumnSeries.new(root, {
+            name,
+            stacked: true,
+            xAxis,
+            yAxis,
+            baseAxis: yAxis,
+            valueXField: fieldName,
+            categoryYField: "year",
+          }));
+  
+          series.columns.template.setAll({
+            tooltipText: "{name}, {categoryY}: {valueX}",
+            tooltipY: am5.percent(90),
+          });
+          series.data.setAll(data);
+  
+          series.appear();
+  
+          series.bullets.push(() =>
+            am5.Bullet.new(root, {
+              sprite: am5.Label.new(root, {
+                text: "{valueX}",
+                fill: root.interfaceColors.get("alternativeText"),
+                centerY: am5.p50,
+                centerX: am5.p50,
+                populateText: true,
+              }),
+            })
+          );
+          
+  
+          legend.data.push(series);
+        };
+   
+        // Create series
+        createSeries("Europe", "europe");
+        createSeries("North America", "namerica");
+        createSeries("Asia", "asia");
+        createSeries("Latin America", "lamerica");
+        createSeries("Middle East", "meast");
+        createSeries("Africa", "africa");
+  
+        // Chart animation
+        chart.appear(1000, 100);
+      },
+    },
+  };
+  </script>
+  
+  <style scoped>
+  /* Add necessary styles here */
+  </style>
+  (No newline at end of file)
client/views/pages/teacher/C_TextBookDetail.vue
--- client/views/pages/teacher/C_TextBookDetail.vue
+++ client/views/pages/teacher/C_TextBookDetail.vue
@@ -1,6 +1,6 @@
 <template>
     <div class="title-box flex justify-between mb40">
-        <p class="title">A교재</p>
+        <p class="title">{{ bookDetails ? bookDetails.book_nm : 'Loading...' }}</p>
         <select name="" id="">
             <option value="">A 반</option>
         </select>
@@ -66,7 +66,7 @@
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiMagnify, mdilArrowRight } from '@mdi/js';
 import ProgressBar from '../../component/ProgressBar.vue';
-
+import axios from 'axios';
 
 export default {
     data() {
@@ -74,7 +74,9 @@
             mdiMagnify: mdiMagnify,
             mdilArrowRight: mdilArrowRight,
             timer: "00:00",
-            progress: 20
+            progress: 20,
+            selectedBookId: this.$route.query.book_Id,
+            bookDetails: null,
         }
     },
     methods: {
@@ -100,6 +102,25 @@
                 this.goBack();
             }
         },
+        fetchBookDetails() {
+            axios({
+                url: "/book/find.json",
+                method: "post",
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    book_id: this.$route.query.book_id
+                }
+            })
+            .then(response => {
+                console.log('Book details:', response.data);
+                this.bookDetails = response.data;
+            })
+            .catch(error => {
+                console.error('Error fetching book details:', error);
+            });
+        }  
     },
     watch: {
 
@@ -112,7 +133,8 @@
         ProgressBar
     },
     mounted() {
-        console.log('Main2 mounted');
+        console.log('Mounted with book_id:', this.$route.query.book_id);
+        this.fetchBookDetails();
     }
 }
 </script>
client/views/pages/teacher/C_Textbook.vue
--- client/views/pages/teacher/C_Textbook.vue
+++ client/views/pages/teacher/C_Textbook.vue
@@ -1,62 +1,67 @@
 <template>
     <div class="title-box flex justify-between mb40">
         <p class="title">교재</p>
-        <select name="" id="">
-            <option value="">A반</option>
+        <select v-model="selectedClassId">
+            <option v-for="classItem in classList" :key="classItem.sclsId" :value="classItem.sclsId">
+                {{ classItem.sclsNm }}
+            </option>
         </select>
-        </div>
-        <div class="content-t">
-            <div  class=" flex " style="gap: 50px;">
-                <div class="textbook">
-                    <div class="box " style="gap: 10px;" @click="goToPage('C_TextBookDetail')">
-                    </div>
-                    <div class="text ">
-                        <p class="title1" style="color: #fff;">A 교재</p>
-                        <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;">
-                            <button>수정</button><p>&#124;</p>
-                            <button @click="showConfirm('delete')">삭제</button>
-                        </div>
-                    </div>
+    </div>
+    <div class="content-t">
+        <div class="flex" style="gap: 2.5%;" :style="{flexWrap: 'wrap'}">
+            <div class="textbook" v-for="textbookItem in textbookList" :key="textbookItem.book_id" style="width: 22.5%; margin-bottom: 30px;">
+                <div class="box" style="gap: 10px;" @click="goToPage('C_TextBookDetail', textbookItem.book_id)">
                 </div>
-                
-                <div class="textbook-add">
-                        <button  @click="buttonSearch"><img src="../../../resources/img/btn32_98t_normal.png" alt=""></button>
-        
+                <div class="text">
+                    <p class="title1" style="color: #fff;">{{ textbookItem.book_nm }}</p>
+                    <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;">
+                        <button @click="deleteBook(textbookItem.book_id)">삭제</button>
+                    </div>
                 </div>
             </div>
-        </div>
-        <div v-show="searchOpen" class="popup-wrap">
-                <div class="popup-box ">
-                    <div class="flex justify-between mb30">
-                        <p class="popup-title">교재 이름</p>
-                        <button type="button" class="popup-close-btn" @click="closeBtn">
-                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
 
-                        </button>
-                    </div>
-                    <div class="search-wrap mb30">
-                        <select v-model="selectedSearchOption" class="mr10 data-wrap">
-      <option value="bbsTtl">제목</option>
-      <option value="bbsCnt">내용</option>
-      <option value="userNm">작성자</option>
-      <option value="bbsCls">카테고리</option>
-    </select>
-                    </div>
-                    <div class="flex justify-center ">
-                        <button type="button" title="글쓰기" class="new-btn mr10">
-                            취소
-                        </button>
-                        <button type="button" title="글쓰기" class="new-btn">
-                            등록
-                        </button>
-                    </div>
-                </div>
+            <div class="textbook-add" style="width: 22.5%; margin-bottom: 30px;">
+                <button @click="buttonSearch">
+                    <img src="../../../resources/img/btn32_98t_normal.png" alt="">
+                </button>
             </div>
+        </div>
+    </div>
+    <div v-show="searchOpen" class="popup-wrap">
+    <div class="popup-box">
+        <div class="flex justify-between mb30">
+            <p class="popup-title">교재 이름</p>
+            <button type="button" class="popup-close-btn" @click="closeBtn">
+                <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+            </button>
+        </div>
+        <div class="search-wrap mb30">
+            <input v-model="searchKeyword" type="text" placeholder="검색하세요." @keyup.enter="bookDataSearch"/>
+            <button type="button" @click="bookDataSearch()" title="교재 검색">
+                <img src="../../../resources/img/look_t.png" alt="" />
+            </button>
+        </div>
+        <div class="table-wrap">
+        <div v-if="searchResults.length">
+            <tbody>
+                <tr v-for="book in searchResults" :key="book.book_id">
+                    <td>{{ book.book_nm }}<button type="button" title="등록" class="new-btn" @click="insertBook(book.book_id)" style="margin-left: 10px;">추가</button></td>
+                </tr>
+        </tbody>
+        </div>
+    <div v-else>
+        <p>검색 결과가 없습니다.</p>
+    </div>
+</div>
+
+    </div>
+</div>
 </template>
 
 <script>
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiMagnify, mdiWindowClose } from '@mdi/js';
+import axios from 'axios';
 
 export default {
     data () {
@@ -64,21 +69,38 @@
             mdiWindowClose: mdiWindowClose,
             showModal: false,
             searchOpen: false,
+            classList: [],
+            textbookList: [],
+            newBookName: '',
+            editMode: false,
+            editBookId: null,
+            selectedClassId: '',
+            searchOpen: false,
+            searchKeyword: '',
+            searchResults: [],
         }
     },
     methods: {
-        goToPage(page) {
-            this.$router.push({ name: page });
-        },
-        closeModal() {
-            this.showModal = false;
+        goToPage(page, book_id) {
+            this.$router.push({ name: page, query: { book_id: book_id } });
         },
         buttonSearch() {
             this.searchOpen = true;
         },
         closeBtn() {
             this.searchOpen = false;
-
+            this.editMode = false;
+            this.editBookId = null;
+            this.newBookName = '';
+        },
+        editBook(book) {
+            this.newBookName = book.book_nm;
+            this.editMode = true;
+            this.editBookId = book.book_id;
+            this.searchOpen = true;
+        },
+        closeModal() {
+            this.showModal = false;
         },
         showConfirm(type) {
             let message = '';
@@ -94,22 +116,131 @@
                 this.goBack();
             }
         },
+        bookDataSearch() {
+            const vm = this;
+            const searchPayload = {
+                keyword: vm.searchKeyword,
+            };
+            axios.post("/book/search.json", searchPayload)
+                .then(function (res) {
+                    console.log("bookDataSearch - response : ", res.data);
+                    vm.searchResults = res.data.result; // 검색 결과 저장
+                })
+                .catch(function (error) {
+                    console.log("bookSearch - error : ", error);
+                    alert("책이 존재하지 않습니다.");
+                });
+        },
+        stdClassesSelectList() {
+            const vm = this;
+            axios({
+                url: "/classes/selectClass.json",
+                method: "post",
+                headers:{
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    userId:"USID_000000000000001"
+                }
+            })
+            .then(function (response) {
+                console.log("classList - response : ", response.data);
+                vm.classList = response.data.data;
+            })
+            .catch(function (error) {
+                console.log("classList - error : ", error);
+                alert("학생 반 조회에 오류가 발생했습니다.");
+            });
+        },
+        bookList() {
+            console.log('Fetching books for class ID:', this.selectedClassId);
+            if (!this.selectedClassId) return;
 
+            const vm = this;
+            axios({
+                url: "/classBook/findAll.json",
+                method: "post",
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    sclsId: this.selectedClassId
+                }
+            })
+            .then(response => {
+                console.log("bookList - response : ", response.data);
+                vm.textbookList = response.data.result;
+            })
+            .catch(error => {
+                console.error('Error fetching books:', error);
+            });
+        },
+        insertBook(bookId) {
+            const bookToAdd = [{
+                bookId: bookId,
+                sclsId: this.selectedClassId
+            }];
+
+            axios.post('/classBook/register.json', bookToAdd)
+                .then(response => {
+                    console.log('Book added successfully:', response.data);
+                    this.bookList();
+                })
+                .catch(error => {
+                    console.error('Error adding book:', error);
+                });
+        },
+        deleteBook(bookId) {
+            if (confirm('삭제하시겠습니까?')) {
+                axios({
+                    url: "/classBook/delete.json",
+                    method: "post",
+                    headers: {
+                        "Content-Type": "application/json; charset=UTF-8",
+                    },
+                    data: {
+                        bookId: bookId,
+                        sclsId: this.selectedClassId
+                    },
+                })
+                .then(response => {
+                    this.bookList();
+                })
+                .catch(error => {
+                    console.error('Error deleting book:', error);
+                });
+            }
+        },
     },
     watch: {
-
-    },
-    computed: {
-
+        selectedClassId(newVal) {
+            if (newVal) {
+                this.bookList();
+            }
+        }
     },
     components: {
         SvgIcon
     },
     mounted() {
         console.log('Main2 mounted');
+        this.stdClassesSelectList();
     }
 }
+
 </script>
+
 <style scoped>
-.textbook{width: 300px;}
+.content-t {
+    flex-wrap: wrap; 
+    height: 90%;
+    overflow-y: scroll;
+}
+.flex {
+    display: flex;
+    flex-wrap: wrap;
+}
+.textbook, .textbook-add {
+    margin-bottom: 30px;
+}
 </style>
(No newline at end of file)
client/views/pages/teacher/QuestionDetail.vue
--- client/views/pages/teacher/QuestionDetail.vue
+++ client/views/pages/teacher/QuestionDetail.vue
@@ -199,6 +199,7 @@
 <script>
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiMagnify } from '@mdi/js';
+import axios from 'axios';
 
 export default {
     data() {
client/views/pages/teacher/TextBookDetail.vue
--- client/views/pages/teacher/TextBookDetail.vue
+++ client/views/pages/teacher/TextBookDetail.vue
@@ -1,18 +1,13 @@
 <template>
     <div class="title-box flex justify-between mb40">
-        <p class="title">A교재</p>
-        <!-- <select name="" id="">
-            <option value="">1단원</option>
-        </select> -->
-    </div>
+    <p class="title">{{ bookDetails ? bookDetails.book_nm : 'Loading...' }}</p>
+  </div>
     <label for="" class="title1">단원</label>
         <div class="unit-pagination flex mt10" style="gap: 10px;">
             <button class="selected-btn">1</button>
             <button>2</button>
             <button>3</button>
-            <button ><svg-icon type="mdi" :path="mdiPlus" style="    padding-top: 6px;
-    width: 30px;
-    height: 30px;"></svg-icon></button>
+            <button ><svg-icon type="mdi" :path="mdiPlus" style=" padding-top: 6px; width: 30px; height: 30px;"></svg-icon></button>
         </div>
     <div class="board-wrap mt30">
         <div class="mb20 ">
@@ -177,57 +172,52 @@
 
 <script>
 import SvgIcon from '@jamescoyle/vue-icon';
-import { mdiMagnify,mdiPlus  } from '@mdi/js';
+import { mdiMagnify, mdiPlus } from '@mdi/js';
 import { mdilArrowRight } from '@mdi/light-js';
 import ProgressBar from '../../component/ProgressBar.vue';
-
+import axios from 'axios';
 
 export default {
     data() {
         return {
-            mdiPlus :mdiPlus ,
+            mdiPlus: mdiPlus,
             mdiMagnify: mdiMagnify,
             mdilArrowRight: mdilArrowRight,
-            timer: "00:00",
-            progress: 20
-        }
+            selectedBookId: this.$route.query.book_Id,
+            bookDetails: null,
+        };
     },
     methods: {
         goToPage(page) {
             this.$router.push({ name: page });
         },
-        increaseProgress() {
-            if (this.progress < 100) {
-                this.progress += 10;
-            }
-        },
-        showConfirm(type) {
-            let message = '';
-            if (type === 'delete') {
-                message = '삭제하시겠습니까?';
-            } else if (type === 'reset') {
-                message = '초기화하시겠습니까?';
-            } else if (type === 'save') {
-                message = '등록하시겠습니까?';
-            }
-
-            if (confirm(message)) {
-                this.goBack();
-            }
-        },
+        fetchBookDetails() {
+            axios({
+                url: "/book/find.json",
+                method: "post",
+                headers: {
+                    "Content-Type": "application/json; charset=UTF-8",
+                },
+                data: {
+                    book_id: this.$route.query.book_id
+                }
+            })
+            .then(response => {
+                console.log('Book details:', response.data);
+                this.bookDetails = response.data;
+            })
+            .catch(error => {
+                console.error('Error fetching book details:', error);
+            });
+        }
     },
-    watch: {
-
-    },
-    computed: {
-
+    mounted() {
+        console.log('Mounted with book_id:', this.$route.query.book_id);
+        this.fetchBookDetails();
     },
     components: {
         SvgIcon,
         ProgressBar
-    },
-    mounted() {
-        console.log('Main2 mounted');
     }
 }
 </script>
client/views/pages/teacher/VocaDetail.vue
--- client/views/pages/teacher/VocaDetail.vue
+++ client/views/pages/teacher/VocaDetail.vue
@@ -1,6 +1,6 @@
 <template>
     <div class="title-box flex justify-between mb40">
-        <p class="title">단어장 등록</p>
+        <p class="title">단어장 수정</p>
     </div>
     <div class="board-wrap">
         <div class="flex align-center mb20">
@@ -36,7 +36,7 @@
         </div>
     </div>
     <div class="flex justify-between mt50">
-        <button type="button" title="글쓰기" class="new-btn" @click="goToPage('TextList')">
+        <button type="button" title="글쓰기" class="new-btn" @click="goToPage('VocaList')">
             목록
         </button>
         <div class="flex">
client/views/pages/teacher/VocaInsert.vue
--- client/views/pages/teacher/VocaInsert.vue
+++ client/views/pages/teacher/VocaInsert.vue
@@ -2,48 +2,56 @@
     <div class="title-box flex justify-between mb40">
         <p class="title">단어장 등록</p>
     </div>
+    <div class="title2 gray flex mb40">{{ titleMessage }}</div>
     <div class="board-wrap">
         <div class="flex align-center mb20">
             <label for="" class="title2">지문</label>
-            <select v-model="selectedSearchOption" class="mr10 data-wrap">
-                <option value="bbsTtl">제목</option>
-                <option value="bbsCnt">내용</option>
-                <option value="userNm">작성자</option>
-                <option value="bbsCls">카테고리</option>
+            <select v-model="selectedTextId" class="mr10 data-wrap">
+                <option v-for="text in texts" :key="text.textId" :value="text.textId">
+                    {{ text.textTtl }}
+                </option>
+            </select>
+        </div>
+        <div class="flex align-center mb20">
+            <label for="" class="title2">단어장 타입</label>
+            <select v-model="selectedWdBookTypeId" class="mr10 data-wrap">
+                <option value="1">단어장 (일반)</option>
+                <option value="2">단어장 (스피킹)</option>
+                <option value="3">단어장 (숏폼)</option>
+                <option value="4">단어장 (카드 뒤집기)</option>
+                <option value="5">단어장 (카드 맞추기)</option>
             </select>
         </div>
         <div class="flex align-center">
             <label for="" class="title2">단어 목록</label>
             <div class="flex-column" style="gap: 10px;">
                 <div class="flex align-center" style="gap: 10px;">
-                    <input type="text" class="data-wrap" placeholder="영어">
-                    <input type="text" class="data-wrap" placeholder="한글">
-                    <button type="button" @click="addThesis">
+                    <input v-model="newWord.eng" type="text" class="data-wrap" placeholder="영어">
+                    <input v-model="newWord.kor" type="text" class="data-wrap" placeholder="한글">
+                    <button type="button" @click="addWord">
                         <img src="../../../resources/img/btn39_120t_normal.png" alt="">
-
                     </button>
                 </div>
-                <div class="flex align-center " style="gap: 10px;" v-for="(thesis, index) in thesised" :key="thesis.id">
-                    <input type="text" class="data-wrap" placeholder="영어">
-                    <input type="text" class="data-wrap" placeholder="한글">
-                    <button type="button" @click="removeThesis(thesis.id)">
-                        <img src="../../../resources/img/btn38_120t_normal.png" alt="">
-                    </button>
 
+                <!-- 여기에 단어장에 소속될 단어들 태그 형태 리스트 -->
+                <div v-for="(word, index) in words" :key="index" class="word-item flex align-center" style="gap: 10px;">
+                    <span>{{ word.eng }} / {{ word.kor }}</span>
+                    <button type="button" @click="removeWord(index)">삭제</button>
                 </div>
+
             </div>
 
         </div>
     </div>
     <div class="flex justify-between mt50">
-        <button type="button" title="글쓰기" class="new-btn" @click="goToPage('TextList')">
+        <button type="button" title="목록" class="new-btn" @click="goToPage('VocaList')">
             목록
         </button>
         <div class="flex">
-            <button type="button" title="글쓰기" class="new-btn mr10">
+            <button type="button" title="취소" class="new-btn mr10" @click="cancelAction">
                 취소
             </button>
-            <button type="button" title="글쓰기" class="new-btn">
+            <button type="button" title="등록" class="new-btn" @click="registerWordBook">
                 등록
             </button>
         </div>
@@ -52,41 +60,199 @@
 
 <script>
 import SvgIcon from '@jamescoyle/vue-icon';
-import { mdiMagnify, mdiPlusCircleOutline, mdiWindowClose } from '@mdi/js';
-
+import axios from "axios";
 
 export default {
     data() {
         return {
-            thesised: [],
-            mdiPlusCircleOutline: mdiPlusCircleOutline,
-            mdiMagnify: mdiMagnify,
-            mdiWindowClose: mdiWindowClose,
+            selectedBookId: null, // 추가될 단어장의 소속 책
+            selectedUnitId: null, // 추가될 단어장의 소속 단원
+            bookName: '', // 책 이름
+            unitName: '', // 단원 이름
+            titleMessage: '', // 등록 경로 메시지
+            texts: [], // 지문 목록
+            selectedTextId: null, // 선택된 지문 ID
+            selectedWdBookTypeId: '1', // 선택된 단어장 타입 ID
+            newWord: { eng: '', kor: '' }, // 입력된 새 단어
+            words: [], // 단어 목록
+            existingWords: [], // 기존 단어 목록 저장
+            userId: "2"
         }
     },
     methods: {
-        // 논문실적 버튼 추가
-        addThesis() {
-            // 고유 ID로 현재 시간의 타임스탬프를 사용
-            const uniqueId = Date.now();
-            this.thesised.push({
-                id: uniqueId, // 고유 ID 추가
 
+        // 책과 단원 이름을 가져오는 메서드
+        fetchBookAndUnitNames() {
+            // 책 이름 가져오기
+            axios.post('/book/findAll.json')
+                .then(response => {
+                    const book = response.data.find(book => book.book_id === this.selectedBookId);
+                    if (book) {
+                        this.bookName = book.book_nm;
+                        this.updateTitleMessage(); // 책 이름을 가져온 후에 제목 업데이트
+                    }
+                })
+                .catch(error => {
+                    console.error("책 이름 가져오기 실패: ", error);
+                });
+
+            // 단원 이름 가져오기
+            axios.post('/unit/unitList.json', { bookId: this.selectedBookId })
+                .then(response => {
+                    const unit = response.data.find(unit => unit.unitId === this.selectedUnitId);
+                    if (unit) {
+                        this.unitName = unit.unitName;
+                        this.updateTitleMessage(); // 단원 이름을 가져온 후에 제목 업데이트
+                    }
+                })
+                .catch(error => {
+                    console.error("단원 이름 가져오기 실패: ", error);
+                });
+        },
+
+        // 등록 경로 메시지를 업데이트하는 메서드
+        updateTitleMessage() {
+            this.titleMessage = `[${this.bookName}]책 > [${this.unitName}]단원`;
+        },
+
+        // 지문 목록을 가져오는 메서드
+        fetchTexts() {
+            axios.post('/text/selectTextList.json', {
+                page: 1,
+                pageSize: 100
+            })
+            .then(response => {
+                this.texts = response.data.texts;
+            })
+            .catch(error => {
+                console.error("지문 목록 가져오기 실패: ", error);
             });
         },
-        removeThesis(thesisId) {
-            // ID를 기준으로 교육 정보 객체를 찾아서 삭제
-            const index = this.thesised.findIndex(thesis => thesis.id === thesisId);
-            if (index !== -1) {
-                this.thesised.splice(index, 1);
+
+        addWord() { // 단어 추가
+            if (this.newWord.eng && this.newWord.kor) {
+                this.words.push({ ...this.newWord });
+                this.newWord.eng = '';
+                this.newWord.kor = '';
+            } else {
+                console.log("단어 입력이 비어 있음");
             }
         },
-        // 
+
+        removeWord(index) { // 단어 삭제
+            this.words.splice(index, 1);
+        },
+
         goToPage(page) {
             this.$router.push({ name: page });
         },
+
+        cancelAction() {
+            this.$router.go(-1);
+        },
+
+        // 기존 단어 조회 메서드
+        fetchExistingWords(wdBookId) {
+            return axios.post('/word/getWordsByBookId.json', { wdBookId: wdBookId })
+                .then(response => {
+                    return response.data.words || [];
+                })
+                .catch(error => {
+                    console.error('기존 단어 목록 가져오기 실패:', error);
+                    return [];
+                });
+        },
+
+        async registerWordBook() {
+            const vm = this;
+
+            try {
+                const response = await axios.post('/wordbook/insert.json', {
+                    wdBookTypeId: vm.selectedWdBookTypeId,
+                    textId: vm.selectedTextId,
+                    userId: vm.userId,
+                    bookId: vm.selectedBookId,
+                    unitId: vm.selectedUnitId
+                });
+
+                const wdBookId = response.data.wdBookId;
+
+                // 기존 단어 목록 조회
+                const existingWords = await vm.fetchExistingWords(wdBookId);
+                vm.existingWords = existingWords;
+
+                const existingWordNames = existingWords.map(word => word.wdNm);
+                const wordsToInsert = [];
+                const wordsToUpdate = [];
+                const wordsToDelete = [];
+
+                // 새로 추가된 단어와 기존 단어 비교
+                vm.words.forEach(word => {
+                    if (existingWordNames.includes(word.eng)) {
+                        wordsToUpdate.push(word);
+                    } else {
+                        wordsToInsert.push(word);
+                    }
+                });
+
+                // 삭제된 단어 목록 찾기
+                existingWords.forEach(word => {
+                    if (!vm.words.find(newWord => newWord.eng === word.wdNm)) {
+                        wordsToDelete.push(word);
+                    }
+                });
+
+                // 단어 삽입
+                for (const word of wordsToInsert) {
+                    await axios.post('/word/insert.json', {
+                        wdBookId: wdBookId,
+                        wdNm: word.eng,
+                        wdMnng: word.kor,
+                        fileMngId: '1'
+                    });
+                }
+
+                // 단어 업데이트
+                for (const word of wordsToUpdate) {
+                    await axios.post('/word/update.json', {
+                        wdBookId: wdBookId,
+                        wdNm: word.eng,
+                        wdMnng: word.kor,
+                        fileMngId: '1'
+                    });
+                }
+
+                // 단어 삭제
+                for (const word of wordsToDelete) {
+                    const wordToDelete = existingWords.find(existingWord => existingWord.wdNm === word.wdNm);
+                    if (wordToDelete) {
+                        await axios.post('/word/delete.json', {
+                            wdBookId: wdBookId,
+                            wdId: wordToDelete.wdId
+                        });
+                    }
+                }
+
+                alert('단어장이 성공적으로 등록되었습니다.');
+                vm.goToPage('VocaList');
+                
+            } catch (error) {
+                console.error('단어장 등록 중 오류 발생:', error);
+                alert('단어장 등록에 실패했습니다.');
+            }
+        }
+
     },
     watch: {
+
+        // 데이터 변경 시 등록 경로 메시지 업데이트
+        selectedBookId() {
+            this.fetchBookAndUnitNames();
+        },
+
+        selectedUnitId() {
+            this.fetchBookAndUnitNames();
+        }
 
     },
     computed: {
@@ -96,7 +262,12 @@
         SvgIcon
     },
     mounted() {
-        console.log('Main2 mounted');
+        console.log('VocaInsert mounted');
+        // 쿼리 전달 받기
+        this.selectedBookId = this.$route.query.selectedBookId || null;
+        this.selectedUnitId = this.$route.query.selectedUnitId || null;
+        this.fetchTexts();
+        this.fetchBookAndUnitNames();
     }
 }
 </script>
(No newline at end of file)
client/views/pages/teacher/VocaList.vue
--- client/views/pages/teacher/VocaList.vue
+++ client/views/pages/teacher/VocaList.vue
@@ -1,25 +1,27 @@
 <template>
     <div class="title-box flex justify-between mb40">
         <p class="title">단어장</p>
-        <select name="" id="">
-            <option value="UNIT_000000000000001">A교재</option>
+        <select v-model="selectedBookId" @change="fetchUnits">
+            <option v-for="book in books" :key="book.book_id" :value="book.book_id">
+                {{ book.book_nm }}
+            </option>
         </select>
     </div>
     <label for="" class="title2">단원</label>
-        <div class="unit-pagination flex mt10 mb20" style="gap: 10px;">
-            <button class="selected-btn">1</button>
-            <button>2</button>
-            <button>3</button>
-        </div>
+    <div class="unit-pagination flex mt10 mb20" style="gap: 10px;">
+        <button v-for="unit in units" :key="unit.unitId" @click="selectUnit(unit.unitId)" :class="{ 'selected-btn': selectedUnitId === unit.unitId }">
+            {{ unit.unitName }}
+        </button>
+    </div>
     <div class="search-wrap flex justify-between mb20 align-center">
-        <div class="title2 gray">?단원 전체 목록</div>
+        <div class="title2 gray flex">{{ titleMessage }}</div>
         <div>
-            <select name="" id="" class="mr10 data-wrap">
-                <option value="">지문</option>
-                <option value="">단어</option>
+            <select v-model="searchType" class="mr10 data-wrap">
+                <option value="text">지문</option>
+                <option value="word">단어</option>
             </select>
-            <input  type="text" placeholder="검색하세요.">
-            <button type="button" title="단어장 검색">
+            <input v-model="searchQuery" type="text" placeholder="검색하세요.">
+            <button type="button" title="단어장 검색" @click="searchWordBooks">
                 <img src="../../../resources/img/look_t.png" alt="">
             </button>
         </div>
@@ -31,15 +33,13 @@
                 <td>지문</td>
                 <td>단어 목록</td>
                 <td>작성자</td>
-                <td>등록일</td>
             </thead>
             <tbody>
                 <tr v-for="(wordBook, index) in dataList" :key="wordBook.wdBookId" @click="goToViewPage('VocaDetail')">
                     <td>{{ createNo(index) }}</td>
                     <td>{{ wordBook.textTtl }}</td>
                     <td>{{ wordBook.wordsPreview }}</td>
-                    <td>{{ wordBook.userId }}</td>
-                    <td>{{ '' }}</td>
+                    <td>{{ wordBook.userNm }}</td>
                 </tr>
             </tbody>
         </table>
@@ -55,7 +55,7 @@
             </button>
         </article>
         <div class="flex justify-end ">
-            <button type="button" title="등록" class="new-btn" @click="goToPage('VocaInsert')">
+            <button type="button" title="등록" class="new-btn" @click="goToViewPage('VocaInsert')">
                 등록
             </button>
         </div>
@@ -72,15 +72,62 @@
     data () {
         return {
             mdiMagnify: mdiMagnify,
+            books: [],
+            units: [],
+            selectedBookId: null, // 선택된 책 ID 저장 변수
+            selectedUnitId: null, // 선택된 단원 ID 저장 변수
+            selectedUnitName: '', // 선택된 단원의 이름 저장 변수
+            titleMessage: '', // 타이틀 메시지 변수
             dataList: [],
             currentPage: 0,
-            itemsPerPage: 2,
+            itemsPerPage: 5,
             totalPosts: 0,
-            unitId: "UNIT_000000000000001"
+            searchType: 'text', // 검색 종류를 저장할 변수
+            searchQuery: '' // 검색어를 저장할 변수
         }
     },
     methods: {
 
+        // 모든 책 목록을 가져오는 메서드
+        fetchBooks() {
+            axios.post('/book/findAll.json')
+            .then(response => {
+                this.books = response.data;
+                if (this.books.length > 0) {
+                    this.selectedBookId = this.books[1].book_id; // 두 번째 책을 선택하도록 기본 설정
+                    this.fetchUnits(); // 책 선택 후 단원 목록 가져오기
+                }
+            })
+            .catch(error => {
+                console.error("책 목록 가져오기 실패: ", error);
+            });
+        },
+
+        // 선택된 책의 단원 목록을 가져오는 메서드
+        fetchUnits() {
+            axios.post('/unit/unitList.json', { bookId: this.selectedBookId })
+            .then(response => {
+                this.units = response.data;
+                if (this.units.length > 0) {
+                    this.selectUnit(this.units[4].unitId); // 다섯 번째 단원을 선택하도록 기본 설정
+                }
+            })
+            .catch(error => {
+                console.error("단원 목록 가져오기 실패: ", error);
+            });
+        },
+
+        // 단원을 선택했을 때 호출되는 메서드
+        selectUnit(unitId) {
+            this.selectedUnitId = unitId;
+            const selectedUnit = this.units.find(unit => unit.unitId === unitId);
+            this.selectedUnitName = selectedUnit ? selectedUnit.unitName : '';
+            this.searchQuery = ''; 
+            this.titleMessage = `[${this.selectedUnitName}] 단원 전체 목록`;
+            this.dataSelectList(); // 단어장 목록 조회
+        },
+
+        // 단어장 목록 조회 메서드
         dataSelectList() {
             const vm = this;
             axios({
@@ -90,7 +137,7 @@
                     "Content-Type": "application/json; charset=UTF-8",
                 },
                 data: {
-                    unitId: vm.unitId,
+                    unitId: vm.selectedUnitId,
                     page: vm.currentPage + 1,
                     pageSize: vm.itemsPerPage
                 },
@@ -134,6 +181,8 @@
                 alert("단어장 목록 조회에 오류가 발생했습니다.");
             });
         },
+
+        // 단어 목록 생략 String 생성 메서드
         generateWordsPreview(words) {
             const maxLength = 20; // 최대 표시 길이 설정
             const wordString = words.join(', ');
@@ -144,9 +193,13 @@
                 return wordString;
             }
         },
+
+        // 단어장 NO 생성 메서드
         createNo(index) {
             return this.totalPosts - (this.currentPage * this.itemsPerPage + index);
         },
+
+        // 페이지네이션 이동 메서드
         goToPage(page) {
             if (page < 0 || page >= this.totalPages) {
                 return;
@@ -154,17 +207,96 @@
             this.currentPage = page;
             this.dataSelectList();
         },
+
+        // 페이지 이동 메서드
         goToViewPage(page) {
-            this.$router.push({ name: page });
+            this.$router.push({
+                name: page,
+                query: {
+                    selectedBookId: this.selectedBookId,
+                    selectedUnitId: this.selectedUnitId
+                }
+             });
+        },
+
+        // 검색 메서드 추가
+        searchWordBooks() {
+            const vm = this;
+            let url = '';
+            let data = {};
+
+            if (this.searchType === 'text') {
+                // 지문으로 검색
+                url = '/wordbook/findByTextTitle.json';
+                data = {
+                    unitId: vm.selectedUnitId,
+                    textTitle: vm.searchQuery,
+                    page: vm.currentPage + 1,
+                    pageSize: vm.itemsPerPage
+                };
+                this.titleMessage = `[${vm.searchQuery}]의 지문 검색 결과`;
+            } else if (this.searchType === 'word') {
+                // 단어로 검색
+                url = '/wordbook/findByWord.json';
+                data = {
+                    unitId: vm.selectedUnitId,
+                    word: vm.searchQuery,
+                    page: vm.currentPage + 1,
+                    pageSize: vm.itemsPerPage
+                };
+                this.titleMessage = `[${vm.searchQuery}]의 단어 검색 결과`;
+            }
+
+            axios.post(url, data)
+            .then(function (response) {
+                console.log("searchWordBooks - response: ", response.data);
+                const wordBooks = response.data.wordBooks;
+                vm.totalPosts = response.data.totalWordBooks;
+                
+                // 지문 제목 및 단어 목록 가져오기
+                const fetchDataPromises = wordBooks.map(wordBook => {
+                    const textTitlePromise = axios.post("/text/selectOneText.json", {
+                        textId: wordBook.textId
+                    }).then(textResponse => {
+                        wordBook.textTtl = textResponse.data[0].text_ttl;
+                    }).catch(error => {
+                        console.error(`${wordBook.textId}으로 지문 제목 가져오기 실패: `, error);
+                        wordBook.textTtl = '제목값없음'; // 오류 시 기본값 설정
+                    });
+
+                    const wordsPromise = axios.post("/word/getWordsByBookId.json", {
+                        wdBookId: wordBook.wdBookId
+                    }).then(wordsResponse => {
+                        const words = wordsResponse.data.map(word => word.wdNm);
+                        wordBook.wordsPreview = vm.generateWordsPreview(words);
+                    }).catch(error => {
+                        console.error(`${wordBook.wdBookId}으로 단어 목록 가져오기 실패: `, error);
+                        wordBook.wordsPreview = '단어값없음'; // 오류 시 기본값 설정
+                    });
+
+                    return Promise.all([textTitlePromise, wordsPromise]);
+                });
+
+                // 모든 데이터 가져오기 작업이 완료되면 dataList에 데이터 설정
+                Promise.all(fetchDataPromises).then(() => {
+                    vm.dataList = wordBooks;
+                });
+            })
+            .catch(function (error) {
+                console.log("searchWordBooks - error: ", error);
+                alert("단어장 검색에 오류가 발생했습니다.");
+            });
         },
     },
     watch: {
 
     },
     computed: {
+
         totalPages() {
             return Math.ceil(this.totalPosts / this.itemsPerPage);
         },
+
         paginationButtons() {
             let start = Math.max(0, this.currentPage - 2);
             let end = Math.min(start + 5, this.totalPages);
@@ -175,16 +307,18 @@
 
             return Array.from({ length: end - start }, (_, i) => start + i + 1);
         },
+
         startIndex() {
             return this.currentPage * this.itemsPerPage;
         }
+
     },
     components:{
         SvgIcon
     },
     mounted() {
         console.log('Voca Book List Component mounted');
-        this.dataSelectList();
+        this.fetchBooks();
     }
 }
 </script>
(No newline at end of file)
client/views/pages/teacher/textbook.vue
--- client/views/pages/teacher/textbook.vue
+++ client/views/pages/teacher/textbook.vue
@@ -1,61 +1,56 @@
 <template>
     <div class="title-box flex justify-between mb40">
-        <p class="title">교재</p>
-        <select name="" id="">
-            <option value="">A반</option>
-        </select>
+      <p class="title">교재</p>
     </div>
     <div class="content-t">
-        <div class="flex" style="gap: 2.5%;" :style="{flexWrap: 'wrap'}">
-            <div class="textbook" v-for="textbookItem in textbookList" :key="textbookItem.book_id" style="width: 22.5%; margin-bottom: 30px;">
-                <div class="box" style="gap: 10px;" @click="goToPage('TextBookDetail', textbookItem.book_id)">
-                </div>
-                <div class="text">
-                    <p class="title1" style="color: #fff;">{{ textbookItem.book_nm }}</p>
-                    <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;">
-                        <button @click="editBook(textbookItem)">수정</button>
-                        <p>&#124;</p>
-                        <button @click="deleteBook(textbookItem.book_id)">삭제</button>
-                    </div>
-                </div>
+      <div class="flex" style="gap: 2.5%;" :style="{flexWrap: 'wrap'}">
+        <div class="textbook" v-for="textbookItem in textbookList" :key="textbookItem.book_id" style="width: 22.5%; margin-bottom: 30px;">
+          <div class="box" style="gap: 10px;" @click="goToPage('TextBookDetail', textbookItem.book_id)">
+          </div>
+          <div class="text">
+            <p class="title1" style="color: #fff;">{{ textbookItem.book_nm }}</p>
+            <div class="btnGroup mt15 flex align-center justify-end" style="gap: 10px;">
+              <button @click="editBook(textbookItem)">수정</button>
+              <p>&#124;</p>
+              <button @click="deleteBook(textbookItem.book_id)">삭제</button>
             </div>
-
-            <div class="textbook-add" style="width: 22.5%; margin-bottom: 30px;">
-                <button @click="buttonSearch">
-                    <img src="../../../resources/img/btn32_98t_normal.png" alt="">
-                </button>
-            </div>
-
-            <div v-show="searchOpen" class="popup-wrap">
-                <div class="popup-box">
-                    <div class="flex justify-between mb30">
-                        <p class="popup-title">교재 이름</p>
-                        <button type="button" class="popup-close-btn" @click="closeBtn">
-                            <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
-                        </button>
-                    </div>
-                    <div class="search-wrap mb30">
-                        <input type="text" class="data-wrap" v-model="newBookName" placeholder="교재 이름을 입력하세요">
-                        <button type="button" title="교재 검색" @click="insertBook">
-                            <img src="../../../resources/img/look_t.png" alt="">
-                        </button>
-                    </div>
-                    <div class="flex justify-center">
-                        <button type="button" title="취소" class="new-btn mr10" @click="closeBtn">
-                            취소
-                        </button>
-                        <button type="button" title="생성" class="new-btn" @click="editMode ? updateBook() : insertBook()">
-                            {{ editMode ? '수정' : '생성' }}
-                        </button>
-                    </div>
-                </div>
-            </div>
+          </div>
         </div>
+  
+        <div class="textbook-add" style="width: 22.5%; margin-bottom: 30px;">
+          <button @click="buttonSearch">
+            <img src="../../../resources/img/btn32_98t_normal.png" alt="">
+          </button>
+        </div>
+  
+        <div v-show="searchOpen" class="popup-wrap">
+          <div class="popup-box">
+            <div class="flex justify-between mb30">
+              <p class="popup-title">교재 이름</p>
+              <button type="button" class="popup-close-btn" @click="closeBtn">
+                <svg-icon type="mdi" :path="mdiWindowClose" class="close-btn"></svg-icon>
+              </button>
+            </div>
+            <div class="search-wrap mb30">
+              <input type="text" class="data-wrap" v-model="newBookName" placeholder="교재 이름을 입력하세요">
+              <button type="button" title="교재 검색" @click="insertBook">
+                <img src="../../../resources/img/look_t.png" alt="">
+              </button>
+            </div>
+            <div class="flex justify-center">
+              <button type="button" title="취소" class="new-btn mr10" @click="closeBtn">
+                취소
+              </button>
+              <button type="button" title="생성" class="new-btn" @click="editMode ? updateBook() : insertBook()">
+                {{ editMode ? '수정' : '생성' }}
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
 </template>
-
-
-
+  
 <script>
 import SvgIcon from '@jamescoyle/vue-icon';
 import { mdiWindowClose } from '@mdi/js';
@@ -75,7 +70,7 @@
     },
     methods: {
         goToPage(page, book_id) {
-            this.$router.push({ name: page, query: { book_id }});
+            this.$router.push({ name: page, query: { book_id: book_id } });
         },
         buttonSearch() {
             this.searchOpen = true;
@@ -160,10 +155,7 @@
                     console.error('Error deleting book:', error);
                 });
             }
-        }
-    },
-    computed: {
-
+        },
     },
     components: {
         SvgIcon
@@ -185,4 +177,4 @@
     display: flex;
     flex-wrap: wrap;
 }
-</style>
(No newline at end of file)
+</style>
package-lock.json
--- package-lock.json
+++ package-lock.json
@@ -5,7 +5,7 @@
   "packages": {
     "": {
       "dependencies": {
-        "@amcharts/amcharts4": "^4.10.39",
+        "@amcharts/amcharts5": "^5.10.1",
         "@babel/cli": "7.19.3",
         "@babel/core": "7.19.3",
         "@jamescoyle/vue-icon": "^0.1.2",
@@ -34,26 +34,37 @@
         "webpack-cli": "^5.1.4"
       }
     },
-    "node_modules/@amcharts/amcharts4": {
-      "version": "4.10.39",
-      "resolved": "https://registry.npmjs.org/@amcharts/amcharts4/-/amcharts4-4.10.39.tgz",
-      "integrity": "sha512-5WbpZgI0m0Mf8Ydwlm1XWB8hIzkk6fJifzYmJqo5HLdA8jCQa+4I+8uOlGlvSMxbBTkvxanEgA2WX27+99X44w==",
+    "node_modules/@amcharts/amcharts5": {
+      "version": "5.10.1",
+      "resolved": "https://registry.npmjs.org/@amcharts/amcharts5/-/amcharts5-5.10.1.tgz",
+      "integrity": "sha512-oGTZ7QJ/AEiMgJ6W3xzX7dSTK47Zl4j44ZsHiWbdAU0BKSQPmzw/jGgj806/ki2Ym4wuxWvE8dCg6josmNnVDg==",
       "license": "SEE LICENSE IN LICENSE",
       "dependencies": {
-        "@babel/runtime": "^7.6.3",
-        "core-js": "^3.0.0",
+        "@types/d3": "^7.0.0",
+        "@types/d3-chord": "^3.0.0",
+        "@types/d3-hierarchy": "3.1.1",
+        "@types/d3-sankey": "^0.11.1",
+        "@types/d3-shape": "^3.0.0",
+        "@types/geojson": "^7946.0.8",
+        "@types/polylabel": "^1.0.5",
+        "@types/svg-arc-to-cubic-bezier": "^3.2.0",
+        "d3": "^7.0.0",
+        "d3-chord": "^3.0.0",
         "d3-force": "^3.0.0",
-        "d3-geo": "^3.0.1",
-        "d3-geo-projection": "^4.0.0",
+        "d3-geo": "^3.0.0",
+        "d3-hierarchy": "^3.0.0",
+        "d3-sankey": "^0.12.3",
         "d3-selection": "^3.0.0",
-        "d3-transition": "^3.0.1",
+        "d3-shape": "^3.0.0",
+        "d3-transition": "^3.0.0",
+        "d3-voronoi-treemap": "^1.1.2",
+        "flatpickr": "^4.6.9",
+        "markerjs2": "^2.29.4",
         "pdfmake": "^0.2.2",
-        "polylabel": "^1.0.2",
-        "raf": "^3.4.1",
-        "regression": "^2.0.1",
-        "rgbcolor": "^1.0.1",
-        "stackblur-canvas": "^2.0.0",
-        "tslib": "^2.0.1"
+        "polylabel": "^1.1.0",
+        "seedrandom": "^3.0.5",
+        "svg-arc-to-cubic-bezier": "^3.2.0",
+        "tslib": "^2.2.0"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -292,18 +303,6 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/@babel/runtime": {
-      "version": "7.25.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
-      "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
-      "license": "MIT",
-      "dependencies": {
-        "regenerator-runtime": "^0.14.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@babel/template": {
       "version": "7.25.0",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
@@ -487,6 +486,283 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/@types/d3": {
+      "version": "7.4.3",
+      "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+      "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-array": "*",
+        "@types/d3-axis": "*",
+        "@types/d3-brush": "*",
+        "@types/d3-chord": "*",
+        "@types/d3-color": "*",
+        "@types/d3-contour": "*",
+        "@types/d3-delaunay": "*",
+        "@types/d3-dispatch": "*",
+        "@types/d3-drag": "*",
+        "@types/d3-dsv": "*",
+        "@types/d3-ease": "*",
+        "@types/d3-fetch": "*",
+        "@types/d3-force": "*",
+        "@types/d3-format": "*",
+        "@types/d3-geo": "*",
+        "@types/d3-hierarchy": "*",
+        "@types/d3-interpolate": "*",
+        "@types/d3-path": "*",
+        "@types/d3-polygon": "*",
+        "@types/d3-quadtree": "*",
+        "@types/d3-random": "*",
+        "@types/d3-scale": "*",
+        "@types/d3-scale-chromatic": "*",
+        "@types/d3-selection": "*",
+        "@types/d3-shape": "*",
+        "@types/d3-time": "*",
+        "@types/d3-time-format": "*",
+        "@types/d3-timer": "*",
+        "@types/d3-transition": "*",
+        "@types/d3-zoom": "*"
+      }
+    },
+    "node_modules/@types/d3-array": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+      "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-axis": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+      "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "node_modules/@types/d3-brush": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+      "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "node_modules/@types/d3-chord": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+      "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-color": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-contour": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+      "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-array": "*",
+        "@types/geojson": "*"
+      }
+    },
+    "node_modules/@types/d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-dispatch": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
+      "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-drag": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+      "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "node_modules/@types/d3-dsv": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+      "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-ease": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+      "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-fetch": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+      "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-dsv": "*"
+      }
+    },
+    "node_modules/@types/d3-force": {
+      "version": "3.0.10",
+      "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+      "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-format": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+      "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-geo": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+      "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/geojson": "*"
+      }
+    },
+    "node_modules/@types/d3-hierarchy": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.1.tgz",
+      "integrity": "sha512-QwjxA3+YCKH3N1Rs3uSiSy1bdxlLB1uUiENXeJudBoAFvtDuswUxLcanoOaR2JYn1melDTuIXR8VhnVyI3yG/A==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-interpolate": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-color": "*"
+      }
+    },
+    "node_modules/@types/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-polygon": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+      "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-quadtree": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+      "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-random": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+      "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-sankey": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-sankey/-/d3-sankey-0.11.2.tgz",
+      "integrity": "sha512-U6SrTWUERSlOhnpSrgvMX64WblX1AxX6nEjI2t3mLK2USpQrnbwYYK+AS9SwiE7wgYmOsSSKoSdr8aoKBH0HgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-shape": "^1"
+      }
+    },
+    "node_modules/@types/d3-sankey/node_modules/@types/d3-path": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz",
+      "integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-sankey/node_modules/@types/d3-shape": {
+      "version": "1.3.12",
+      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz",
+      "integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-path": "^1"
+      }
+    },
+    "node_modules/@types/d3-scale": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
+      "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-time": "*"
+      }
+    },
+    "node_modules/@types/d3-scale-chromatic": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
+      "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-selection": {
+      "version": "3.0.10",
+      "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz",
+      "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-shape": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
+      "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-path": "*"
+      }
+    },
+    "node_modules/@types/d3-time": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
+      "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-time-format": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+      "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-timer": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+      "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-transition": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz",
+      "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-selection": "*"
+      }
+    },
+    "node_modules/@types/d3-zoom": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+      "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-interpolate": "*",
+        "@types/d3-selection": "*"
+      }
+    },
     "node_modules/@types/eslint": {
       "version": "9.6.0",
       "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz",
@@ -512,6 +788,12 @@
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
       "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
     },
+    "node_modules/@types/geojson": {
+      "version": "7946.0.14",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
+      "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==",
+      "license": "MIT"
+    },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -526,6 +808,18 @@
       "dependencies": {
         "undici-types": "~6.11.1"
       }
+    },
+    "node_modules/@types/polylabel": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@types/polylabel/-/polylabel-1.1.3.tgz",
+      "integrity": "sha512-9Zw2KoDpi+T4PZz2G6pO2xArE0m/GSMTW1MIxF2s8ZY8x9XDO6fv9um0ydRGvcbkFLlaq8yNK6eZxnmMZtDgWQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/svg-arc-to-cubic-bezier": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/@types/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.2.tgz",
+      "integrity": "sha512-XQtKy9lmkKlV+c3Jelo7kxNPw7qOqIq3GcnOhywGZHF7zw5D5m+Ssigbmf3Turbe/A8Ur+lRh8TYjuxXKvyivw==",
+      "license": "MIT"
     },
     "node_modules/@vue/compiler-core": {
       "version": "3.2.40",
@@ -1342,17 +1636,6 @@
       "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
       "license": "MIT"
     },
-    "node_modules/core-js": {
-      "version": "3.38.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.0.tgz",
-      "integrity": "sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==",
-      "hasInstallScript": true,
-      "license": "MIT",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/core-js"
-      }
-    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1430,6 +1713,47 @@
       "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==",
       "license": "MIT"
     },
+    "node_modules/d3": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+      "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "3",
+        "d3-axis": "3",
+        "d3-brush": "3",
+        "d3-chord": "3",
+        "d3-color": "3",
+        "d3-contour": "4",
+        "d3-delaunay": "6",
+        "d3-dispatch": "3",
+        "d3-drag": "3",
+        "d3-dsv": "3",
+        "d3-ease": "3",
+        "d3-fetch": "3",
+        "d3-force": "3",
+        "d3-format": "3",
+        "d3-geo": "3",
+        "d3-hierarchy": "3",
+        "d3-interpolate": "3",
+        "d3-path": "3",
+        "d3-polygon": "3",
+        "d3-quadtree": "3",
+        "d3-random": "3",
+        "d3-scale": "4",
+        "d3-scale-chromatic": "3",
+        "d3-selection": "3",
+        "d3-shape": "3",
+        "d3-time": "3",
+        "d3-time-format": "4",
+        "d3-timer": "3",
+        "d3-transition": "3",
+        "d3-zoom": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/d3-array": {
       "version": "3.2.4",
       "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
@@ -1437,6 +1761,43 @@
       "license": "ISC",
       "dependencies": {
         "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-axis": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+      "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-brush": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+      "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "3",
+        "d3-transition": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-chord": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+      "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-path": "1 - 3"
       },
       "engines": {
         "node": ">=12"
@@ -1451,6 +1812,30 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-contour": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+      "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "^3.2.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+      "license": "ISC",
+      "dependencies": {
+        "delaunator": "5"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/d3-dispatch": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
@@ -1460,11 +1845,82 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-drag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+      "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-selection": "3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dsv": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+      "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+      "license": "ISC",
+      "dependencies": {
+        "commander": "7",
+        "iconv-lite": "0.6",
+        "rw": "1"
+      },
+      "bin": {
+        "csv2json": "bin/dsv2json.js",
+        "csv2tsv": "bin/dsv2dsv.js",
+        "dsv2dsv": "bin/dsv2dsv.js",
+        "dsv2json": "bin/dsv2json.js",
+        "json2csv": "bin/json2dsv.js",
+        "json2dsv": "bin/json2dsv.js",
+        "json2tsv": "bin/json2dsv.js",
+        "tsv2csv": "bin/dsv2dsv.js",
+        "tsv2json": "bin/dsv2json.js"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dsv/node_modules/commander": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/d3-dsv/node_modules/iconv-lite": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/d3-ease": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
       "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
       "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-fetch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+      "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dsv": "1 - 3"
+      },
       "engines": {
         "node": ">=12"
       }
@@ -1483,6 +1939,15 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-format": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/d3-geo": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
@@ -1495,34 +1960,13 @@
         "node": ">=12"
       }
     },
-    "node_modules/d3-geo-projection": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz",
-      "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==",
+    "node_modules/d3-hierarchy": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+      "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
       "license": "ISC",
-      "dependencies": {
-        "commander": "7",
-        "d3-array": "1 - 3",
-        "d3-geo": "1.12.0 - 3"
-      },
-      "bin": {
-        "geo2svg": "bin/geo2svg.js",
-        "geograticule": "bin/geograticule.js",
-        "geoproject": "bin/geoproject.js",
-        "geoquantize": "bin/geoquantize.js",
-        "geostitch": "bin/geostitch.js"
-      },
       "engines": {
         "node": ">=12"
-      }
-    },
-    "node_modules/d3-geo-projection/node_modules/commander": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
-      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 10"
       }
     },
     "node_modules/d3-interpolate": {
@@ -1537,6 +1981,24 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-polygon": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+      "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/d3-quadtree": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
@@ -1546,11 +2008,125 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-random": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+      "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-sankey": {
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+      "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-array": "1 - 2",
+        "d3-shape": "^1.2.0"
+      }
+    },
+    "node_modules/d3-sankey/node_modules/d3-array": {
+      "version": "2.12.1",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+      "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "internmap": "^1.0.0"
+      }
+    },
+    "node_modules/d3-sankey/node_modules/d3-path": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+      "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-sankey/node_modules/d3-shape": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+      "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-path": "1"
+      }
+    },
+    "node_modules/d3-sankey/node_modules/internmap": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+      "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+      "license": "ISC"
+    },
+    "node_modules/d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale-chromatic": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+      "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3",
+        "d3-interpolate": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/d3-selection": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
       "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
       "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time-format": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-time": "1 - 3"
+      },
       "engines": {
         "node": ">=12"
       }
@@ -1581,6 +2157,92 @@
       },
       "peerDependencies": {
         "d3-selection": "2 - 3"
+      }
+    },
+    "node_modules/d3-voronoi-map": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/d3-voronoi-map/-/d3-voronoi-map-2.1.1.tgz",
+      "integrity": "sha512-mCXfz/kD9IQxjHaU2IMjkO8fSo4J6oysPR2iL+omDsCy1i1Qn6BQ/e4hEAW8C6ms2kfuHwqtbNom80Hih94YsA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-dispatch": "2.*",
+        "d3-polygon": "2.*",
+        "d3-timer": "2.*",
+        "d3-weighted-voronoi": "1.*"
+      }
+    },
+    "node_modules/d3-voronoi-map/node_modules/d3-dispatch": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz",
+      "integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-voronoi-map/node_modules/d3-polygon": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz",
+      "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-voronoi-map/node_modules/d3-timer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz",
+      "integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-voronoi-treemap": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/d3-voronoi-treemap/-/d3-voronoi-treemap-1.1.2.tgz",
+      "integrity": "sha512-7odu9HdG/yLPWwzDteJq4yd9Q/NwgQV7IE/u36VQtcCK7k1sZwDqbkHCeMKNTBsq5mQjDwolTsrXcU0j8ZEMCA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-voronoi-map": "2.*"
+      }
+    },
+    "node_modules/d3-weighted-voronoi": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/d3-weighted-voronoi/-/d3-weighted-voronoi-1.1.3.tgz",
+      "integrity": "sha512-C3WdvSKl9aqhAy+f3QT3PPsQG6V+ajDfYO3BSclQDSD+araW2xDBFIH67aKzsSuuuKaX8K2y2dGq1fq/dWTVig==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-array": "2",
+        "d3-polygon": "2"
+      }
+    },
+    "node_modules/d3-weighted-voronoi/node_modules/d3-array": {
+      "version": "2.12.1",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+      "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "internmap": "^1.0.0"
+      }
+    },
+    "node_modules/d3-weighted-voronoi/node_modules/d3-polygon": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz",
+      "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-weighted-voronoi/node_modules/internmap": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+      "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+      "license": "ISC"
+    },
+    "node_modules/d3-zoom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+      "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-drag": "2 - 3",
+        "d3-interpolate": "1 - 3",
+        "d3-selection": "2 - 3",
+        "d3-transition": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
       }
     },
     "node_modules/debug": {
@@ -1652,6 +2314,15 @@
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/delaunator": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
+      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+      "license": "ISC",
+      "dependencies": {
+        "robust-predicates": "^3.0.2"
       }
     },
     "node_modules/delayed-stream": {
@@ -2105,6 +2776,12 @@
       "bin": {
         "flat": "cli.js"
       }
+    },
+    "node_modules/flatpickr": {
+      "version": "4.6.13",
+      "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz",
+      "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==",
+      "license": "MIT"
     },
     "node_modules/follow-redirects": {
       "version": "1.15.6",
@@ -2786,6 +3463,12 @@
         "semver": "bin/semver"
       }
     },
+    "node_modules/markerjs2": {
+      "version": "2.32.1",
+      "resolved": "https://registry.npmjs.org/markerjs2/-/markerjs2-2.32.1.tgz",
+      "integrity": "sha512-OGBINMGhXwTXZF/k0ky9vciPm8C3/bsDZUJroZrIvoX0xv3OWYBEDiUSmgRpiLkCv5Z4Q7RaYxhza/iafc25zw==",
+      "license": "SEE LICENSE IN LICENSE"
+    },
     "node_modules/media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -3113,12 +3796,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/performance-now": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-      "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
-      "license": "MIT"
-    },
     "node_modules/pg": {
       "version": "8.8.0",
       "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
@@ -3441,15 +4118,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/raf": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
-      "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
-      "license": "MIT",
-      "dependencies": {
-        "performance-now": "^2.1.0"
-      }
-    },
     "node_modules/randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -3508,12 +4176,6 @@
         "node": ">= 10.13.0"
       }
     },
-    "node_modules/regenerator-runtime": {
-      "version": "0.14.1",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
-      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
-      "license": "MIT"
-    },
     "node_modules/regexp.prototype.flags": {
       "version": "1.5.2",
       "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
@@ -3531,12 +4193,6 @@
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
-    },
-    "node_modules/regression": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz",
-      "integrity": "sha512-A4XYsc37dsBaNOgEjkJKzfJlE394IMmUPlI/p3TTI9u3T+2a+eox5Pr/CPUqF0eszeWZJPAc6QkroAhuUpWDJQ==",
-      "license": "MIT"
     },
     "node_modules/resolve": {
       "version": "1.22.8",
@@ -3578,14 +4234,17 @@
         "node": ">=8"
       }
     },
-    "node_modules/rgbcolor": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
-      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
-      "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
-      "engines": {
-        "node": ">= 0.8.15"
-      }
+    "node_modules/robust-predicates": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+      "license": "Unlicense"
+    },
+    "node_modules/rw": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+      "license": "BSD-3-Clause"
     },
     "node_modules/safe-buffer": {
       "version": "5.2.1",
@@ -3636,6 +4295,12 @@
         "type": "opencollective",
         "url": "https://opencollective.com/webpack"
       }
+    },
+    "node_modules/seedrandom": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
+      "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
+      "license": "MIT"
     },
     "node_modules/semver": {
       "version": "6.3.1",
@@ -3860,15 +4525,6 @@
         "node": ">= 10.x"
       }
     },
-    "node_modules/stackblur-canvas": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
-      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.1.14"
-      }
-    },
     "node_modules/statuses": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -3902,6 +4558,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/svg-arc-to-cubic-bezier": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
+      "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
+      "license": "ISC"
+    },
     "node_modules/tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
package.json
--- package.json
+++ package.json
@@ -1,6 +1,6 @@
 {
   "dependencies": {
-    "@amcharts/amcharts4": "^4.10.39",
+    "@amcharts/amcharts5": "^5.10.1",
     "@babel/cli": "7.19.3",
     "@babel/core": "7.19.3",
     "@jamescoyle/vue-icon": "^0.1.2",
webpack.config.js
--- webpack.config.js
+++ webpack.config.js
@@ -3,6 +3,7 @@
 const {PROJECT_NAME, BASE_DIR, SERVICE_STATUS} = require('./Global');
 
 module.exports = {
+  
   name: PROJECT_NAME,
   mode: SERVICE_STATUS,
   devtool: 'eval',
@@ -12,6 +13,7 @@
   },
 
   module: {
+    
     rules: [{
       test: /\.vue?$/,
       loader: 'vue-loader',
@@ -39,4 +41,11 @@
     path: `${BASE_DIR}/client/build`,	// __dirname: webpack.config.js 파일이 위치한 경로
     filename: 'bundle.js'
   },
+  configureWebpack: {
+    resolve: {
+      alias: {
+        "@amcharts/amcharts5": "@amcharts/amcharts5/dist/amcharts5.esm.js"
+      }
+    }
+  },
 }
(No newline at end of file)
Add a comment
List