hgw8837 2024-12-30
20241230 하관우 front_end 최초커밋
@77e8f0e744f147eda30b5a6ae557b5d5e7b04cf1
 
.gitignore (added)
+++ .gitignore
@@ -0,0 +1,3 @@
+client/build/
+server/logs/
+node_modules/(파일 끝에 줄바꿈 문자 없음)
 
.vscode/launch.json (added)
+++ .vscode/launch.json
@@ -0,0 +1,15 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "msedge",
+            "request": "launch",
+            "name": "Launch Edge against localhost",
+            "url": "http://localhost:8081",
+            "webRoot": "${workspaceFolder}"
+        }
+    ]
+}(파일 끝에 줄바꿈 문자 없음)
 
Global.js (added)
+++ Global.js
@@ -0,0 +1,17 @@
+const PROJECT_NAME = 'NodeJS Web Server Framework(Vue)';
+const PROJECT_VERSION = '1.0';
+const BASE_DIR = __dirname;
+const LOG_BASE_DIR = `${__dirname}/server/logs`;
+const SERVICE_STATUS = process.env.NODE_ENV; // development, production
+const PORT = 8080;
+const API_SERVER_HOST = "localhost:9090";
+
+module.exports = {
+  PROJECT_NAME,
+  PROJECT_VERSION,
+  BASE_DIR,
+  LOG_BASE_DIR,
+  SERVICE_STATUS,
+  PORT,
+  API_SERVER_HOST
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/common.css (added)
+++ client/resources/css/common.css
@@ -0,0 +1,905 @@
+@charset "utf-8";
+
+:root {
+    /* color */
+    --color-black: #3f454d;
+    --color-gray: #c6c6c6;
+    --color-white: #ffffff;
+    --color-orange: #fbbe28;
+    --color-light-orange: #fffcf2;
+    --color-green: #13833b;
+    --color-red: #ff5d5d;
+    --color-blue: #213f99;
+    --color-darkG: #434343;
+    --color-blueE: #e3ecff;
+    --color-skyBlue: #f5f8ff;
+}
+
+/* 정렬 */
+.flex {
+    display: flex;
+    flex-wrap: wrap;
+}
+
+.flex-column {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+}
+
+
+.justify-start {
+    justify-content: flex-start;
+}
+
+.justify-center {
+    justify-content: center;
+}
+
+.justify-between {
+    justify-content: space-between;
+}
+
+.justify-around {
+    justify-content: space-around;
+}
+
+.justify-end {
+    justify-content: flex-end;
+}
+
+.align-start {
+    align-items: flex-start;
+}
+
+.align-center {
+    align-items: center;
+}
+
+.align-end {
+    align-items: flex-end;
+}
+
+
+.flex5,
+.flex10,
+.flex15,
+.flex20,
+.flex25,
+.flex30,
+.flex35,
+.flex40,
+.flex45,
+.flex50,
+.flex55,
+.flex60,
+.flex65,
+.flex70,
+.flex75,
+.flex80,
+.flex85,
+.flex90,
+.flex95,
+.flex100 {
+    padding-left: 10px;
+    padding-right: 10px;
+}
+
+.no-gutter>.flex5,
+.no-gutter>.flex10,
+.no-gutter>.flex15,
+.no-gutter>.flex20,
+.no-gutter>.flex25,
+.no-gutter>.flex30,
+.no-gutter>.flex35,
+.no-gutter>.flex40,
+.no-gutter>.flex45,
+.no-gutter>.flex50,
+.no-gutter>.flex55,
+.no-gutter>.flex60,
+.no-gutter>.flex65,
+.no-gutter>.flex70,
+.no-gutter>.flex75,
+.no-gutter>.flex80,
+.no-gutter>.flex85,
+.no-gutter>.flex90,
+.no-gutter>.flex95,
+.no-gutter>.flex100 {
+    padding-left: 0;
+    padding-right: 0;
+}
+
+/* 정렬 배율 */
+.flex5 {
+    flex: 0 0 5%
+}
+
+.flex10 {
+    flex: 0 0 10%
+}
+
+.flex15 {
+    flex: 0 0 15%
+}
+
+.flex20 {
+    flex: 0 0 20%
+}
+
+.flex25 {
+    flex: 0 0 25%
+}
+
+.flex30 {
+    flex: 0 0 30%
+}
+
+.flex35 {
+    flex: 0 0 35%
+}
+
+.flex40 {
+    flex: 0 0 40%
+}
+
+.flex45 {
+    flex: 0 0 45%
+}
+
+.flex50 {
+    flex: 0 0 50%
+}
+
+.flex55 {
+    flex: 0 0 55%
+}
+
+.flex60 {
+    flex: 0 0 60%
+}
+
+.flex65 {
+    flex: 0 0 65%
+}
+
+.flex70 {
+    flex: 0 0 70%
+}
+
+.flex75 {
+    flex: 0 0 75%
+}
+
+.flex80 {
+    flex: 0 0 80%
+}
+
+.flex85 {
+    flex: 0 0 85%
+}
+
+
+.flex90 {
+    flex: 0 0 90%
+}
+
+.flex95 {
+    flex: 0 0 95%
+}
+
+.flex100 {
+    flex: 0 0 100%;
+}
+
+/* 너비 */
+.w10 {
+    width: 10%;
+}
+
+.w20 {
+    width: 20%;
+}
+
+.w30 {
+    width: 30%;
+}
+
+.w40 {
+    width: 40%;
+}
+
+.w50 {
+    width: 50%;
+}
+
+.w60 {
+    width: 60%;
+}
+
+.w70 {
+    width: 70%;
+}
+
+.w80 {
+    width: 80%;
+}
+
+.w90 {
+    width: 90%;
+}
+
+.w100 {
+    width: 100%;
+}
+
+
+
+/* 마진 */
+.ml0 {
+    margin-left: 0px;
+}
+
+.ml5 {
+    margin-left: 5px;
+}
+
+.ml10 {
+    margin-left: 10px;
+}
+
+.ml20 {
+    margin-left: 20px;
+}
+
+.ml30 {
+    margin-left: 30px;
+}
+
+.ml40 {
+    margin-left: 40px;
+}
+
+.ml50 {
+    margin-left: 50px;
+}
+
+.ml60 {
+    margin-left: 60px;
+}
+
+.ml70 {
+    margin-left: 70px;
+}
+
+.ml80 {
+    margin-left: 80px;
+}
+
+.ml90 {
+    margin-left: 90px;
+}
+
+.ml100 {
+    margin-left: 100px;
+}
+
+.mr0 {
+    margin-right: 0px;
+}
+
+.mr5 {
+    margin-right: 5px;
+}
+
+.mr10 {
+    margin-right: 10px;
+}
+
+.mr20 {
+    margin-right: 20px;
+}
+
+.mr30 {
+    margin-right: 30px;
+}
+
+.mr40 {
+    margin-right: 40px;
+}
+
+.mr50 {
+    margin-right: 50px;
+}
+
+.mr60 {
+    margin-right: 60px;
+}
+
+.mr70 {
+    margin-right: 70px;
+}
+
+.mr80 {
+    margin-right: 80px;
+}
+
+.mr90 {
+    margin-right: 90px;
+}
+
+.mr100 {
+    margin-right: 100px;
+}
+
+.mt0 {
+    margin-top: 0px;
+}
+
+.mt5 {
+    margin-top: 5px;
+}
+
+.mt10 {
+    margin-top: 10px;
+}
+
+.mt20 {
+    margin-top: 20px;
+}
+
+.mt30 {
+    margin-top: 30px;
+}
+
+.mt40 {
+    margin-top: 40px;
+}
+
+.mt50 {
+    margin-top: 50px;
+}
+
+.mt60 {
+    margin-top: 60px;
+}
+
+.mt70 {
+    margin-top: 70px;
+}
+
+.mt80 {
+    margin-top: 80px;
+}
+
+.mt90 {
+    margin-top: 90px;
+}
+
+.mt100 {
+    margin-top: 100px;
+}
+
+.mb0 {
+    margin-bottom: 0px;
+}
+
+.mb5 {
+    margin-bottom: 5px;
+}
+
+.mb10 {
+    margin-bottom: 10px;
+}
+
+.mb20 {
+    margin-bottom: 20px;
+}
+
+.mb30 {
+    margin-bottom: 30px;
+}
+
+.mb40 {
+    margin-bottom: 40px;
+}
+
+.mb50 {
+    margin-bottom: 50px;
+}
+
+.mb60 {
+    margin-bottom: 60px;
+}
+
+.mb70 {
+    margin-bottom: 70px;
+}
+
+.mb80 {
+    margin-bottom: 80px;
+}
+
+.mb90 {
+    margin-bottom: 90px;
+}
+
+.mb100 {
+    margin-bottom: 100px;
+}
+
+/* 패딩 */
+.pd0 {
+    padding: 0;
+}
+
+.pd5 {
+    padding: 5px;
+}
+
+.pd10 {
+    padding: 10px;
+}
+
+.pd20 {
+    padding: 20px;
+}
+
+.pd30 {
+    padding: 30px;
+}
+
+.pd40 {
+    padding: 40px;
+}
+
+.pd50 {
+    padding: 50px;
+}
+
+.pd60 {
+    padding: 60px;
+}
+
+.pd70 {
+    padding: 70px;
+}
+
+.pd80 {
+    padding: 80px;
+}
+
+.pd90 {
+    padding: 90px;
+}
+
+.pd100 {
+    padding: 100px;
+}
+
+.pt0 {
+    padding-top: 0;
+}
+
+.pt5 {
+    padding-top: 5px;
+}
+
+.pt10 {
+    padding-top: 10px;
+}
+
+.pt20 {
+    padding-top: 20px;
+}
+
+.pt30 {
+    padding-top: 30px;
+}
+
+.pt40 {
+    padding-top: 40px;
+}
+
+.pt50 {
+    padding-top: 50px;
+}
+
+.pt60 {
+    padding-top: 60px;
+}
+
+.pt70 {
+    padding-top: 70px;
+}
+
+.pt80 {
+    padding-top: 80px;
+}
+
+.pt90 {
+    padding-top: 90px;
+}
+
+.pt100 {
+    padding-top: 100px;
+}
+
+.pb0 {
+    padding-bottom: 0;
+}
+
+.pb5 {
+    padding-bottom: 5px;
+}
+
+.pb10 {
+    padding-bottom: 10px;
+}
+
+.pb20 {
+    padding-bottom: 20px;
+}
+
+.pb30 {
+    padding-bottom: 30px;
+}
+
+.pb40 {
+    padding-bottom: 40px;
+}
+
+.pb50 {
+    padding-bottom: 50px;
+}
+
+.pb60 {
+    padding-bottom: 60px;
+}
+
+.pb70 {
+    padding-bottom: 70px;
+}
+
+.pb80 {
+    padding-bottom: 80px;
+}
+
+.pb90 {
+    padding-bottom: 90px;
+}
+
+.pb100 {
+    padding-bottom: 100px;
+}
+
+.pl0 {
+    padding-left: 0;
+}
+
+.pl5 {
+    padding-left: 5px;
+}
+
+.pl10 {
+    padding-left: 10px;
+}
+
+.pl20 {
+    padding-left: 20px;
+}
+
+.pl30 {
+    padding-left: 30px;
+}
+
+.pl40 {
+    padding-left: 40px;
+}
+
+.pl50 {
+    padding-left: 50px;
+}
+
+.pl60 {
+    padding-left: 60px;
+}
+
+.pl70 {
+    padding-left: 70px;
+}
+
+.pl80 {
+    padding-left: 80px;
+}
+
+.pl90 {
+    padding-left: 90px;
+}
+
+.pl100 {
+    padding-left: 100px;
+}
+
+
+.pr0 {
+    padding-right: 0;
+}
+
+.pr5 {
+    padding-right: 5px;
+}
+
+.pr10 {
+    padding-right: 10px;
+}
+
+.pr20 {
+    padding-right: 20px;
+}
+
+.pr30 {
+    padding-right: 30px;
+}
+
+.pr40 {
+    padding-right: 40px;
+}
+
+.pr50 {
+    padding-right: 50px;
+}
+
+.pr60 {
+    padding-right: 60px;
+}
+
+.pr70 {
+    padding-right: 70px;
+}
+
+.pr80 {
+    padding-right: 80px;
+}
+
+.pr90 {
+    padding-right: 90px;
+}
+
+.pr100 {
+    padding-right: 100px;
+}
+
+/* btn */
+.small-btn {
+    width: 120px;
+    padding: 5px 10px;
+    border-radius: 5px;
+}
+
+.set-btn {
+    width: 50px;
+    padding: 5px 10px;
+    border-radius: 5px;
+}
+
+.large-btn {
+    width: 100%;
+    padding: 5px 15px;
+    border-radius: 5px;
+}
+
+.icon-btn {
+    padding: 5px;
+    border-radius: 50%;
+
+}
+
+.custom-toggle {
+    position: absolute;
+    bottom: 0;
+    background-color: var(--color-skyBlue);
+    margin-left: 0;
+    border: 1px solid #eee;
+    border-left: 0;
+}
+
+.custom-toggle::after {
+    content: "";
+    position: absolute;
+    left: 0;
+    width: 5px;
+    height: 5px;
+    background-color: var(--color-skyBlue);
+    bottom: 0;
+}
+
+.logout-btn {
+    padding: 5px 10px;
+    color: #aaa;
+    position: relative;
+}
+
+.logout-btn::before {
+    content: "";
+    width: 1px;
+    height: 10px;
+    position: absolute;
+    top: 50%;
+    left: 0;
+    transform: translateY(-50%);
+    background-color: #aaa;
+}
+
+.close-btn {
+    color: #d6def6;
+}
+
+.attribute-btn {
+    position: relative;
+}
+
+.blue-btn,
+.blue-border-btn:hover {
+    background-color: var(--color-blue);
+    color: var(--color-white);
+    transition: all 0.3s ease-in-out;
+}
+
+.red-btn,
+.red-border-btn:hover {
+    background-color: var(--color-red);
+    color: var(--color-white);
+    transition: all 0.3s ease-in-out;
+}
+
+.green-btn,
+.green-border-btn:hover {
+    background-color: var(--color-green);
+    color: var(--color-white);
+    transition: all 0.3s ease-in-out;
+}
+
+.orange-btn,
+.orange-border-btn:hover {
+    background-color: var(--color-orange);
+    color: var(--color-white);
+    transition: all 0.3s ease-in-out;
+}
+
+.darkg-btn,
+.darkg-border-btn:hover {
+    background-color: var(--color-darkG);
+    color: var(--color-white);
+    transition: all 0.3s ease-in-out;
+}
+
+.gray-btn,
+.gray-border-btn:hover {
+    background-color: #eee;
+    color: #333;
+    transition: all 0.3s ease-in-out;
+}
+
+
+.blue-border-btn {
+    border: 1px solid var(--color-blue);
+    color: var(--color-blue);
+    background-color: var(--color-white);
+}
+
+.red-border-btn {
+    border: 1px solid var(--color-red);
+    color: var(--color-red);
+    background-color: var(--color-white);
+}
+
+.green-border-btn {
+    border: 1px solid var(--color-green);
+    color: var(--color-green);
+    background-color: var(--color-white);
+}
+
+.orange-border-btn {
+    border: 1px solid var(--color-orange);
+    color: var(--color-orange);
+    background-color: #fff;
+}
+
+.darkg-border-btn {
+    border: 1px solid #434343;
+    color: #434343;
+    background-color: var(--color-white);
+}
+
+.gray-border-btn {
+    border: 1px solid #aaa;
+    color: #aaa;
+    background-color: var(--color-white);
+}
+
+.tp-btn {
+    background-color: transparent;
+    width: 15px;
+    height: 15px;
+    margin-left: 10px;
+}
+
+button:disabled {
+    background-color: #eee;
+    color: #333;
+}
+
+
+/* text 정렬 */
+.text-lf {
+    text-align: left;
+}
+
+.text-ct {
+    text-align: center;
+}
+
+.text-rg {
+    text-align: right;
+}
+
+/* text color */
+.orange {
+    color: var(--color-orange);
+}
+
+.green {
+    color: var(--color-green);
+}
+
+.blue {
+    color: var(--color-blue);
+}
+
+.red {
+    color: var(--color-red);
+}
+
+.cursor {
+    cursor: pointer;
+}
+
+/** 로딩 화면 */
+.loading-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, 0.5); /* 반투명 검은색 배경 */
+    display: flex;
+    z-index: 100001; /* 다른 요소 위에 표시되도록 z-index를 높게 설정 (모달창이 100000라 1 더 높게 설정 */
+}
+.loading-div {
+    position: absolute;
+    display: flex;
+    top: 50%;
+    left: 52%;
+    transform: translate(-50%, -50%);
+    color: white;
+    font-size: 32px;
+    height: 100px;
+    /* animation: bounce 0.1s infinite alternate; */
+}
+
+.loading-div .anima {
+    animation: bounce 1.2s infinite;
+}
+
+/* 각 span에 대한 지연 시간 추가 */
+.loading-div span:nth-child(2) { animation-delay: 0.1s; }
+.loading-div span:nth-child(3) { animation-delay: 0.4s; }
+.loading-div span:nth-child(4) { animation-delay: 0.7s; }
+@keyframes bounce {
+    0%, 100% {
+        transform: translateY(0);
+    }
+    50% {
+        transform: translateY(-15px);
+    }
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/component.css (added)
+++ client/resources/css/component.css
@@ -0,0 +1,805 @@
+@charset "utf-8";
+
+/* box 공통 */
+.container {
+    width: 100%;
+    height: 100%;
+}
+
+.content-box {
+    width: 100%;
+    height: 100%;
+}
+
+.w100{
+    width: 100%;
+}
+
+.left-content,
+.right-content {
+    height: 100%;
+}
+
+.content-wrap {
+    height: calc(100% - 47px);
+}
+
+.content {
+    width: 100%;
+    overflow-y: auto;
+}
+
+.content:last-child {
+    margin-bottom: 0;
+}
+
+.content-zone {
+    height: calc(100% - 57px);
+}
+
+.left-content,
+.right-content,
+.row,
+.content {
+    padding: 10px 0;
+    border-radius: 10px;
+    background-color: #fff;
+    position: relative;
+}
+
+.row {
+    padding: 0;
+}
+
+.form-box {
+    background-color: #edf0ff;
+    border: 1px solid #dbe3fb;
+    padding: 15px;
+    border-radius: 5px;
+}
+
+
+.border {
+    border: 1px solid #eee;
+    border-radius: 5px;
+}
+
+.border.active {
+    border: 1px solid var(--color-blue);
+    border-radius: 5px;
+}
+
+.overflow-y {
+    overflow-y: auto;
+}
+.overflow-x {
+    overflow-x: auto;
+}
+
+/* title 공통 */
+.page-titleZone {
+    margin-bottom: 10px;
+}
+
+.main-title {
+    font-family: 'GmarketSansB';
+    font-size: 2rem;
+    color: #213f99;
+}
+
+.content-titleZone {
+    padding-bottom: 10px;
+    border-bottom: 1px solid #aaa;
+    margin-bottom: 10px;
+}
+
+.content-titleZone2 {
+    margin-bottom: 10px;
+}
+
+.box-title {
+    font-size: 1.6rem;
+    font-weight: bold;
+    position: relative;
+}
+
+.content-titleZone2 > .box-title::before{
+    content: '';
+    width: 3px;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background-color: var(--color-blue);
+    margin-right: 1rem;
+}
+
+.object-title {
+    font-size: 1.4rem;
+    font-weight: bold;
+    color: var(--color-blue);
+}
+
+.detail-text{
+    font-size: 1.3rem;
+    font-weight: 400;
+}
+.detail-bold{
+    font-size: 1.3rem;
+    font-weight: 700;
+}
+
+
+/* 테이블 공통 */
+.table-zone {
+    padding: 15px 0;
+}
+
+
+.form-table,
+.list-table2 {
+    border-bottom: 1px solid #bbb;
+}
+.sticky-table th,
+.sticky-table td,
+.list-table th,
+.list-table td,
+.list-table2 th,
+.list-table2 td,
+.form-table th {
+    text-align: center;
+}
+
+.form-table th,
+.form-table td,
+.list-table2 td {
+    border-top: 1px solid #bbb;
+}
+.sticky-table thead th,
+.list-table thead th,
+.list-table2 thead th {
+    background-color: #dbe3fb;
+    color: #213f99;
+}
+
+.list-table.orange thead tr,
+.sticky-table.orange thead tr {
+    background-color: #f29600;
+    color: #fff;
+}
+
+.list-table tbody tr,
+.list-table2 tbody tr,
+.sticky-table tbody tr {
+    cursor: pointer;
+}
+
+.list-table tbody tr:hover,
+.list-table2 tbody tr:hover,
+.sticky-table tbody tr:hover {
+    background-color: var(--color-light-orange);
+}
+
+.list-table tbody tr:nth-child(even)
+/* .sticky-table tbody tr:nth-child(even) */ {
+    background-color: #f4f6ff;
+}
+
+.list-table.orange tbody tr:nth-child(even),
+.sticky-table.orange tbody tr:nth-child(even) {
+    background-color: #fff6e8;
+}
+
+.form-table th {
+    color: #213f99;
+    text-align: center;
+    background-color: #dbe3fb;
+}
+
+.sticky-table thead th{
+    position: sticky;
+    top: 0;
+}
+
+.form-table2 th,
+.custom-subtitle {
+    color: #213f99;
+    /* text-align: center; */
+    font-weight: 800;
+}
+
+.option-table th {
+    color: #213f99;
+}
+
+.list-info {
+    margin-bottom: 10px;
+}
+
+.no-list {
+    text-align: center;
+    font-size: 1.3rem;
+    line-height: 120px;
+}
+
+.modal-table {
+    border-bottom: 0;
+}
+
+.modal-table td {
+    border-top: 0;
+}
+
+/* 서치바 공통 */
+.searchbar-zone {
+    margin-bottom: 10px;
+}
+
+/* 기본 서치바 */
+.search-square {
+    position: relative;
+    margin-left: 5px;
+    display: flex;
+    align-items: center;
+}
+
+.square-date,
+.square-select {
+    width: 150px;
+}
+
+input[type="text"].square-input {
+    color: #646464;
+    padding: 6px 10px;
+    border-radius: 5px 0 0 5px;
+    width: 311px;
+    transition: all ease-in-out .5s;
+}
+
+.square-input:hover,
+.square-input:focus {
+    box-shadow: 0 0 1em #00000013;
+}
+
+.square-input:focus {
+    outline: none;
+    background-color: #f0eeee;
+}
+
+.square-input::-webkit-input-placeholder {
+    font-weight: 100;
+    color: #ccc;
+}
+
+
+.square-button {
+    margin-left: 0;
+    border-radius: 0 5px 5px 0;
+    padding: 2.5px 0;
+    /* border: none;
+
+    position: absolute;
+    right: 5px;
+    top: 50%;
+    transform: translateY(-50%); */
+}
+
+.square-button:hover {
+    cursor: pointer;
+}
+
+.square-icon {
+    color: #b4b4b4;
+}
+
+input[type="text"].full-input,
+input[type="password"].full-input,
+.full-select {
+    width: 100%;
+}
+
+input[type="text"].half-input,
+input[type="password"].half-input,
+.half-select {
+    width: 50%;
+}
+
+/* 상세 서치바 */
+.search-bottom {
+    padding: 10px 0;
+}
+
+.option-searchbar {
+    width: 75%;
+    margin: 0 auto;
+    padding: 15px;
+    background-color: #f8f9fe;
+    border-radius: 10px;
+}
+
+.search-top {
+    padding: 15px 0;
+    border-bottom: 1px solid #aaa;
+}
+
+/* radio css */
+.input-container label {
+    display: flex;
+    cursor: pointer;
+    font-weight: 500;
+    position: relative;
+    overflow: hidden;
+    margin-bottom: 3px;
+}
+
+.input-container label input.custom-radiobox,
+.input-container label input.custom-checkbox {
+    position: absolute;
+    left: -9999px;
+}
+
+.input-container label input.custom-radiobox:checked+span {
+    background-color: #5b72b8;
+    color: #fff;
+}
+
+.input-container label input.custom-checkbox:checked+span {
+    background-color: #f8bb59;
+    color: #fff;
+}
+
+.input-container label input.custom-radiobox:checked+span:before {
+    box-shadow: inset 0 0 0 4px #213f99;
+}
+
+.input-container label input.custom-checkbox:checked+span:before {
+    box-shadow: inset 0 0 0 4px #ff9d00;
+}
+
+.input-container label span {
+    display: flex;
+    align-items: center;
+    padding: 3px 7px;
+    border-radius: 10px;
+    transition: 0.25s ease;
+    color: #333;
+}
+
+
+.input-container label.radio-label span:hover {
+    background-color: #d6d6e5;
+}
+
+.input-container label.check-label span:hover {
+    background-color: #f4e3c2;
+}
+
+.input-container label.radio-label span:before,
+.input-container label.check-label span:before {
+    display: flex;
+    flex-shrink: 0;
+    content: "";
+    background-color: #fff;
+    width: 15px;
+    height: 15px;
+    border-radius: 50%;
+    margin-right: 3px;
+    transition: 0.25s ease;
+    box-shadow: inset 0 0 0 1px #333;
+}
+
+.input-container label.check-label span:before {
+    border-radius: 0%;
+}
+
+/* 기타 공통 */
+.coupler {
+    font-size: 1.3rem;
+    margin-left: 5px;
+}
+
+.option-title {
+    padding: 0 5px;
+    font-size: 1.3rem;
+    color: #213f99;
+    margin-left: 5px;
+}
+
+.count-zone {
+    font-size: 1.3rem;
+}
+
+.count-zone span {
+    font-weight: 900;
+    color: #213f99;
+}
+
+
+/* 모달 */
+.modal-wrapper {
+    background-color: rgba(0, 0, 0, 0.5);
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    left: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 100000;
+}
+
+.modal-container {
+    background: #fff;
+    min-width: 500px;
+    border-radius: 5px;
+    display: grid;
+    grid-template-rows: auto 1fr auto;
+    padding: 20px;
+    box-sizing: border-box;
+    max-height: 95%;
+    min-height: 500px;
+}
+
+.modal-title {
+    width: 100%;
+    border-bottom: 1px solid #d4cccc;
+    padding: 0 0 10px 0;
+}
+
+.modal-subtitle {
+    font-size: 1.3rem;
+    font-weight: 600;
+}
+
+.modal-content-monthly {
+    width: 100%;
+    padding: 13px 0 0 0;
+    overflow-y: auto;
+    font-size: 13px;
+}
+.modal-content-monthly > p{font-size: 14px;}
+.large-modal {
+    width: 90%;
+}
+
+.small-modal {
+    min-width: 350px;
+    min-height: 200px;
+    /* max-width: 450px;
+    height: auto;
+    max-height: 50%; */
+}
+
+.list-modal {
+    width: 80%;
+    height: 80%;
+}
+
+.alert-write {
+    font-size: 1.6rem;
+    line-height: 180%;
+}
+
+
+.modal-content-monthly::-webkit-scrollbar {
+    width: 10px;
+}
+
+.modal-content-monthly::-webkit-scrollbar-thumb {
+    background-color: #6b6b6b;
+    border-radius: 10px;
+    background-clip: padding-box;
+    border: 2px solid transparent;
+}
+
+.modal-content-monthly::-webkit-scrollbar-track {
+    background-color: #eee;
+    border-radius: 10px;
+    box-shadow: inset 0px 0px 5px white;
+}
+
+.modal-end {
+    width: 100%;
+    padding: 15px 0 0 0;
+    border-top: 1px solid #eee;
+    gap: 20px;
+}
+
+.alert-modal .modal-end button,
+.small-modal .modal-end button {
+    margin-left: 0;
+}
+
+
+/* 탭 */
+.tab-nav {
+    border-top: 1px solid #eee;
+    border-bottom: 1px solid #eee;
+    padding: 10px 0;
+}
+
+.tab-nav li a {
+    display: block;
+    font-size: 1.6rem;
+    text-align: center;
+    border-right: 1px solid #eee;
+    padding: 0 10px;
+}
+
+.tab-nav li a.activeTab {
+    color: #213f99;
+    font-weight: 600;
+}
+
+.tab-nav li:last-child a {
+    border-right: 0;
+}
+
+.tab-nav2 {
+    border-bottom: 1px solid #eee;
+}
+
+.tab-nav2 li a {
+    display: block;
+    font-size: 1.6rem;
+    text-align: center;
+    border: 1px solid #eee;
+    padding: 0 10px;
+    background-color: #f8f8f8;
+    padding: 10px;
+    border-radius: 5px 5px 0 0;
+    border-bottom: 5px solid #f8f8f8;
+}
+
+.tab-nav2 li a.activeTab {
+    color: var(--color-blue);
+    font-weight: 600;
+    background-color: #fff;
+    border-bottom: 5px solid var(--color-blue);
+}
+
+.column-nav {
+    height: 100%;
+    background-color: var(--color-blue);
+    padding: 10px 0;
+}
+
+.column-nav ul li a {
+    display: block;
+    font-size: 1.1rem;
+    text-align: center;
+    padding: 10px;
+    color: var(--color-gray);
+}
+
+.column-nav ul li a.activeTab {
+    color: var(--color-white);
+    font-weight: bold;
+}
+
+.tab-content {
+    height: 100%;
+    overflow-y: auto;
+}
+
+.tab-content2 {
+    height: calc(100% - 46px);
+}
+
+.tab-content2 .content-box {
+    padding: 10px;
+}
+
+.tabnav2>ul>li {
+    border: 1px solid var(--color-blue);
+    border-radius: 5px;
+    color: var(--color-blue);
+    background-color: var(--color-white);
+}
+
+.tabnav2>ul>li p,
+.tabnav3 > ul > li > p {
+    font-size: 1.3rem;
+    padding: 8px 10px;
+}
+
+.tabnav2>ul>li p.activeTab2 {
+    background-color: var(--color-blue);
+    color: var(--color-white);
+}
+
+.tab-content2 {
+    padding: 10px 0;
+}
+
+.tabnav3{
+    border-bottom: 1px solid var(--color-blue);
+}
+.tabnav3 > ul > li{
+    border: 1px solid var(--color-blue);
+    border-bottom: 0;
+    border-radius: 5px 5px 0 0;
+    color: var(--color-blue);
+    background-color: var(--color-white);
+}
+
+.tabnav3 > ul > li > p.activeOption {
+    background-color: var(--color-blue);
+    color: var(--color-white);
+    border-radius: 5px 5px 0 0;
+}
+
+
+/* 라벨 css(상세 조회랑 다름) */
+.chekck-type {
+    display: none;
+}
+
+.chekcktype-label {
+    border-radius: 5px;
+    padding: 5px 10px;
+    background-color: #d6def6;
+    color: #213f99;
+}
+
+.chekck-type:checked+label {
+    background-color: #213f99;
+    color: #fff;
+}
+
+
+/* file tree */
+.file-list {
+    display: block;
+    padding: 5px;
+    font-size: 1.3rem;
+}
+
+.selected {
+    background-color: rgb(255, 249, 239);
+    border: 1px solid var(--color-orange);
+    color: var(--color-orange);
+    border-radius: 5px;
+}
+
+.item {
+    background-color: var(--color-white);
+    border-radius: 5px;
+    padding: 5px;
+    width: 100%;
+    border: 1px solid #eee;
+}
+
+.item>p {
+    font-size: 1.3rem;
+}
+
+
+/* 스타일 컴포넌트 */
+.color-picker {
+    width: 40px;
+    height: 40px;
+    background-color: #fff;
+    border-radius: 5px;
+    padding: 0 2px;
+    position: relative;
+}
+
+.color-picker::after {
+    content: "+";
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    background-color: var(--color-white);
+    color: #6b6b6b;
+    border-radius: 20px;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    text-align: center;
+    font-size: 2rem;
+    line-height: 20px;
+}
+
+.up,
+.down {
+    border: 1px solid #aaa;
+    text-align: center;
+    padding: 1.5px 0;
+    background-color: var(--color-white);
+}
+
+.up {
+    border-radius: 0 5px 5px 0;
+    border-left: none;
+}
+
+.down {
+    border-radius: 5px 0 0 5px;
+    border-right: none;
+}
+
+.up button,
+.down button {
+    font-size: 2rem;
+}
+
+input[type="text"].size-input {
+    border-radius: 0;
+    text-align: center;
+}
+
+.color-list {
+    width: 30px;
+    height: 30px;
+    border-radius: 5px;
+}
+
+.check-group:first-child{
+    margin-left: 0;
+}
+
+.style-label {
+    border: 1px solid #eee;
+    border-radius: 3px;
+    background-color: var(--color-white);
+}
+
+.style-label>svg {
+    color: #aaa;
+}
+
+
+.style-input{
+    display: none;
+}
+.style-input:checked + .style-label {
+    border: 1px solid var(--color-blue);
+}
+.style-input:checked + .style-label svg{
+    color: var(--color-blue);
+}
+
+/* 투명도 */
+.slider-container {
+    position: relative;
+}
+
+.slider {
+    width: 100%;
+    background: linear-gradient(to right, red var(--slider-percentage), transparent var(--slider-percentage));
+}
+
+.slider::-webkit-slider-runnable-track {
+    background: linear-gradient(to right, red var(--slider-percentage), transparent var(--slider-percentage));
+}
+
+.component-title-zone {
+    display: table;
+}
+
+.component-maintitle {
+    font-size: 1.8rem;
+    /* font-weight: bold; */
+    position: relative;
+    padding-left: 10px;
+    margin-right: 10px;
+
+}
+
+.component-maintitle::before {
+    content: "";
+    position: absolute;
+    width: 3px;
+    height: 100%;
+    background-color: var(--color-blue);
+    left: 0px;
+}
+
+.component-subtitle {
+    font-size: 1.4rem;
+    color: #aaa;
+    display: table-cell;
+    vertical-align: middle;
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/font.css (added)
+++ client/resources/css/font.css
@@ -0,0 +1,27 @@
+@font-face {
+    font-family: 'Pretendard';
+    src: url('../font/PretendardVariable.woff2') format('woff');
+    font-weight: 400;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: "GmarketSansM";
+    src: url("/client/resources/font/GmarketSansMedium.woff") format("woff");
+    font-weight: normal;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: "GmarketSansL";
+    src: url("/client/resources/font/GmarketSansLight.woff") format("woff");
+    font-weight: normal;
+    font-style: normal;
+}
+
+@font-face {
+    font-family: "GmarketSansB";
+    src: url("/client/resources/font/GmarketSansBold.woff") format("woff");
+    font-weight: bold;
+    font-style: normal;
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/layout.css (added)
+++ client/resources/css/layout.css
@@ -0,0 +1,199 @@
+@charset "utf-8";
+
+.dashboard-wrap {
+    width: 100%;
+    height: 100vh;
+    display: grid;
+    grid-template-columns: 270px minmax(auto, 1fr);
+    grid-template-rows: auto 1fr;
+    grid-template-areas:
+        "header header "
+        "nav  main  "
+        "nav  main  "
+}
+
+.layout-wrap {
+    width: 100%;
+    min-height: 100vh;
+    position: relative;
+
+}
+
+
+header {
+    background-color: #fff;
+    padding: 15px 30px;
+    grid-area: header;
+    position: relative;
+}
+
+.logo {
+    width: 200px;
+}
+
+.logo>a {
+    display: block;
+    width: 100%;
+}
+
+.logo>a>img {
+    display: block;
+    width: 100%;
+}
+
+.user-name {
+    font-size: 1.3rem;
+    margin-left: 5px;
+}
+
+.sms,
+.user {
+    margin-left: 10px;
+}
+
+.layout-wrap header{
+    position: absolute;
+    width: 100%;
+    top: 0;
+    left: 0;
+    z-index: 1;
+}
+
+
+
+/* 메뉴 공통 */
+nav {
+    background-color: #213f99;
+    position: relative;
+}
+
+nav ul li a,
+nav ul li p {
+    padding: 5px;
+    display: block;
+    color: #fff;
+}
+
+nav.side-menu ul.sub-menu,
+nav.top-menu ul.sub-menu {
+    font-size: 1.3rem;
+    overflow: hidden;
+    transition: all 0.5s ease-in-out;
+}
+
+ul.sub-menu>li {
+    padding: 10px 20px;
+
+}
+
+p.active {
+    background-color: #fff;
+    color: #213f99;
+    border-radius: 50px
+}
+
+/* 사이드 메뉴 */
+nav.side-menu {
+    width: 100%;
+    height: 100vh;
+    border-radius: 0 70px 70px 0;
+    grid-area: nav;
+    overflow-y: auto;
+}
+
+nav.side-menu::-webkit-scrollbar {
+    display: none;
+}
+
+nav.side-menu::-ms-scrollbar {
+    display: none;
+}
+
+
+nav.side-menu>ul.main-menu {
+    padding: 50px 30px;
+}
+
+nav.side-menu>ul.main-menu>li>div>a,
+nav>ul>li {
+    padding: 10px 0;
+    font-size: 1.3rem;
+    font-weight: bold;
+}
+
+
+
+/* 상단 메뉴 */
+.top-menu{
+    position: absolute;
+    width: 100%;
+    top: 57px;
+    left: 0;
+    z-index: 2;
+}
+.top-menu>ul.main-menu {
+    display: flex;
+    justify-content: center;
+}
+
+.depth1 {
+    cursor: default;
+}
+
+.top-menu>ul>li {
+    /* min-width: 152px; */
+    /* padding: 10px 30px; */
+    text-align: center;
+    position: relative;
+}
+
+.top-menu>ul>li>p {
+    padding: 5px 47px;
+}
+
+.top-menu ul.sub-menu {
+    position: absolute;
+    width: 100%;
+    background-color: #fff;
+    z-index: 4;
+    bottom: 0;
+    left: 0;
+    transform: translateY(100%);
+    transition: max-height 1.8s ease-in-out, opacity 1.3s ease-in-out;
+}
+
+.top-menu ul.sub-menu li a {
+    text-align: center;
+    color: #213f99;
+}
+
+.navbg {
+    overflow: hidden;
+    background-color: #fff;
+    border-bottom: 2px solid #213f99;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    transform: translateY(100%);
+    width: 100%;
+    z-index: 3;
+    transition: height 0.6s ease-in-out;
+}
+
+
+
+/* 메인 */
+.main {
+    padding: 20px;
+    height: 100vh;
+    grid-area: main;
+}
+
+.layout-wrap .main {
+    padding: 123px 20px 20px;
+}
+
+.login{
+    width: 100%;
+    height: 100vh;
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/reset.css (added)
+++ client/resources/css/reset.css
@@ -0,0 +1,148 @@
+@charset "utf-8";
+
+* {
+    padding: 0;
+    margin: 0;
+    box-sizing: border-box;
+}
+
+
+html,
+body,
+#root {
+    width: 100%;
+    min-height: 100vh;
+    font-size: 10px;
+    color: var(--color-black);
+    font-family: 'Pretendard';
+}
+
+
+html{
+    -ms-user-select: none;
+    -moz-user-select: -moz-none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    user-select: none;
+}
+
+body {
+    min-width: 1356px;
+    background-color: #f7f6fb;
+}
+
+
+a {
+    color: #333;
+    text-decoration: none;
+}
+
+ol,
+ul,
+li {
+    list-style: none;
+}
+
+img,
+svg {
+    vertical-align: middle;
+}
+table {
+    min-width: 100%;
+    border-collapse: collapse;
+    table-layout: fixed;
+}
+
+table th,
+table td {
+    padding: 8px;
+    font-size: 1.3rem;
+}
+
+button {
+    border: none;
+    background-color: transparent;
+    font-size: 1.3rem;
+    margin-left: 5px;
+    cursor: pointer;
+}
+
+label {
+    display: block;
+    font-size: 1.3rem;
+}
+
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="date"],
+input[type="number"] {
+    padding: 5px 10px;
+    border: 1px solid #aaa;
+    border-radius: 5px;
+    font-size: 1.3rem;
+    position: relative;
+}
+
+input:focus,
+select:focus {
+    outline: none;
+}
+
+input:disabled {
+    background: #aaa;
+    border: none;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+    vertical-align: sub;
+}
+
+input[type='date'] {
+    max-width: 240px;
+    padding: 5px;
+}
+
+input[type='date'].date-placeholder::before {
+    position: absolute;
+    content: attr(data-placeholder);
+    width: calc(100% - 42px);
+    display: block;
+    color: #ddd;
+    top: 7px;
+    left: 5px;
+    background-color: var(--color-white);
+}
+
+button:first-child,
+select:first-child,
+input:first-child,
+button.only,
+select.only,
+input.only {
+    margin-left: 0;
+}
+
+textarea {
+    width: 100%;
+    height: 100%;
+}
+
+
+/* 스크롤바 디자인 */
+::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+}
+
+::-webkit-scrollbar-thumb {
+    background-color: #ededed;
+    border-radius: 10px;
+}
+
+::-webkit-scrollbar-track {
+    background-color: #fff;
+    border-radius: 10px;
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/responsive.css (added)
+++ client/resources/css/responsive.css
@@ -0,0 +1,15 @@
+/* @media screen and (min-width: 1537px){
+
+
+}
+
+@media screen and (min-width:1356px) and (max-width: 1536px) {
+	html{
+        font-size: 8.5px;
+    }
+    button {
+        font-size: 1.2rem;
+    }
+
+
+}  */(파일 끝에 줄바꿈 문자 없음)
 
client/resources/css/style.css (added)
+++ client/resources/css/style.css
@@ -0,0 +1,504 @@
+@charset "utf-8";
+
+/* page 추가 */
+/* 작업관리 페이지*/
+.node-zone {
+    margin-bottom: 10px;
+    background-color: #fff;
+    padding: 10px;
+    border-radius: 5px;
+}
+
+.text-over {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.state span {
+    font-size: 1.3rem;
+}
+
+.vue-flow__panel button {
+    margin-left: 0;
+}
+
+/* 파일관리 페이지*/
+.file-zone {
+    height: calc(100% - 55px);
+}
+
+.file-tree-zone {
+    height: calc(100% - 79px);
+}
+
+.tree-wrap {
+    height: 100%;
+    /* overflow: auto; */
+}
+
+.file-table th {
+    position: sticky;
+    top: 0;
+}
+
+.file-table tr .icon-btn {
+    opacity: 0;
+}
+
+.file-table tr:hover,
+.file-table tr:nth-child(even):hover {
+    background-color: var(--color-light-orange);
+}
+
+.file-table tr:hover .icon-btn {
+    opacity: 1;
+    transition: all 0.5s ease-in-out;
+}
+
+
+/* 데이터활용관리 */
+.gall-list li {
+    padding: 0 10px;
+    max-width: 25%;
+}
+
+.gall-list li>div {
+    min-height: 274px;
+    padding: 10px;
+    box-shadow: 0 0 5px #aaa;
+}
+
+.gall-list li>div a {
+    display: block;
+    width: 100%;
+}
+
+.gall-list li .gall-img {
+    width: 100%;
+    height: 200px;
+    text-align: center;
+    overflow: hidden;
+    margin-bottom: 10px;
+}
+
+.gall-list li .gall-img img {
+    width: 100%;
+    height: 100%;
+}
+
+.gall-list li .gall-title {
+    font-size: 1.6rem;
+    font-weight: 800;
+    margin-bottom: 10px;
+}
+
+.gall-info {
+    width: 100%;
+}
+
+.gall-list li .gall-detail {
+    font-size: 1.3rem;
+    width: 100%;
+    min-height: 50px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    word-break: break-all;
+}
+
+.custom-info {
+    position: absolute;
+    width: calc(100% - 0px);
+    z-index: 1;
+}
+
+.layout-option,
+.layout {
+    padding: 15px 0;
+    height: 100%;
+}
+
+.custom-info details summary,
+.component-zone details summary,
+.chart-zone details summary {
+    font-size: 1.6rem;
+    padding: 15px;
+    border: 1px solid #213f99;
+    background-color: rgb(243, 246, 255);
+    border-radius: 5px;
+    position: relative;
+}
+
+.component-zone details summary,
+.chart-zone details summary {
+    border: 1px solid var(--color-orange);
+    background-color: rgb(255, 249, 239);
+    color: var(--color-orange);
+}
+
+.custom-info details[open] summary,
+.component-zone details[open] summary,
+.chart-zone details[open] summary {
+    border-radius: 5px 5px 0 0;
+}
+
+
+.custom-info .info-zone {
+    border: 1px solid #eee;
+    border-radius: 0 0 5px 5px;
+    padding: 15px;
+    background-color: #fff;
+}
+
+.data-list {
+    height: calc(100% - 47px);
+    overflow-y: auto;
+    background-color: #f8f8f8;
+    border-radius: 5px;
+    padding: 10px;
+}
+
+.tab-zone {
+    position: relative;
+}
+
+.tab-zone,
+.preview-zone {
+    height: 100%;
+}
+
+.column-item {
+    width: 100%;
+    font-size: 1.3rem;
+    text-align: center;
+    padding: 10px;
+    border-radius: 5px;
+    border: 1px solid var(--color-orange);
+    background-color: rgb(255, 249, 239);
+    color: var(--color-orange);
+    margin-bottom: 10px;
+}
+
+.column-item:last-child {
+    margin-bottom: 0;
+}
+
+.component-content {
+    padding: 10px;
+
+}
+
+.layout-content>li,
+.component-content>li {
+    margin-top: 10px;
+    margin-right: 10px;
+}
+
+.layout-content>li:nth-child(3n),
+.component-content>li:nth-child(3n) {
+    margin-right: 0px;
+}
+
+.icon-content {
+    margin-left: 10px;
+    font-size: 1.3rem;
+}
+
+.component-wrap {
+    width: 100%;
+    height: 100%;
+    padding: 10px;
+    border-radius: 5px;
+    background-color: #f8f8f8;
+}
+
+input[type="text"].com-dbZone {
+    margin-left: 0;
+}
+
+.db-input {
+    height: 100%;
+}
+
+.active-layout .vertical-icon,
+.active-layout .horizental-icon {
+    background-color: var(--color-blueE);
+    border: 3px solid var(--color-blue);
+}
+
+.active-layout .horizental-icon>span,
+.active-layout .vertical-icon>span {
+    border: 3px solid var(--color-blue);
+}
+
+.component-content li img {
+    width: 100%;
+}
+
+.page-info>.info-area,
+.layout-tree ul {
+    height: calc(100% - 40px);
+    overflow-y: auto;
+}
+
+.layout-option {
+    background-color: var(--color-skyBlue);
+
+}
+
+.layout-option>div:nth-child(2) {
+    overflow-y: auto;
+}
+
+.section-title {
+    font-size: 1.4rem;
+    position: relative;
+}
+
+.section-title::before {
+    content: "";
+    position: absolute;
+    width: 100%;
+    height: 50%;
+    background-color: var(--color-orange);
+    left: 0;
+    bottom: 0;
+    opacity: 0.2;
+}
+
+.detail-content {
+    background-color: var(--color-white);
+    border: 1px solid var(--color-blue);
+    border-radius: 0 0 5px 5px;
+    border-top: 0;
+}
+
+.tab-box {
+    border: 1px solid #eee;
+    border-radius: 0 5px 0 0;
+}
+
+.attribute-modal {
+    position: absolute;
+    min-width: 120px;
+    max-width: 200px;
+    bottom: 0;
+    left: 0;
+    background-color: var(--color-white);
+    transform: translateY(100%);
+    border: 1px solid #ddd;
+    z-index: 3;
+}
+
+.attribute-modal>ul>li {
+    padding: 8px 5px;
+    border-top: 1px solid #eee;
+}
+
+.attribute-modal>ul>li:first-child {
+    border-top: 0;
+}
+
+.attribute-modal>ul>li svg,
+.attribute-modal>ul>li p {
+    color: var(--color-darkG);
+}
+
+.attribute-modal>ul>li:hover svg,
+.attribute-modal>ul>li:hover p {
+    color: var(--color-blue);
+}
+
+.editor-box {
+    background-color: var(--color-skyBlue);
+    border: 1px dashed var(--color-blue);
+}
+
+.data-set {
+    height: 30%;
+}
+
+.column-list {
+    height: 70%;
+}
+
+#horizontal-btn,
+#vertical-btn {
+    width: 18px;
+    height: 18px;
+}
+
+#horizontal-btn>img,
+#vertical-btn>img {
+    width: 100%;
+}
+
+
+/* 알람 */
+
+.speaker {
+    text-align: center;
+    margin-bottom: 10px;
+}
+
+.speaker>span {
+    display: inline-block;
+    background-color: #eee;
+}
+
+.speaker>span:nth-of-type(1) {
+    width: 10px;
+    height: 10px;
+    border-radius: 10px;
+}
+
+.speaker>span:nth-of-type(2) {
+    width: 60px;
+    height: 10px;
+    border-radius: 10px;
+    margin-left: 10px;
+}
+
+.text-areaZone {
+    height: calc(50% - 22px);
+    margin-bottom: 10px;
+}
+
+.user-list {
+    width: 100%;
+    height: calc(100% - 50% - 22px);
+}
+
+.user-list .user-title {
+    font-size: 1.6rem;
+    font-weight: 800;
+    color: var(--color-blue);
+    margin-bottom: 10px;
+}
+
+.user-list>ul {
+    padding: 10px;
+    height: calc(100% - 29px);
+    overflow-y: auto;
+    border-top: 1px solid #eee;
+}
+
+.user-list>ul>li {
+    padding: 5px 10px;
+    margin-bottom: 10px;
+    border: 1px solid var(--color-orange);
+    background-color: rgb(255, 249, 239);
+    color: var(--color-orange);
+    border-radius: 5px;
+    font-size: 1.3rem;
+}
+
+.user-list>ul>li:last-child {
+    margin-bottom: 0;
+}
+
+.user-zone,
+.log-zone {
+    height: 100vh;
+}
+
+.all-user,
+.check-user {
+    height: 100vh;
+    border: 1px solid #eee;
+}
+
+.log-content {
+    height: 99%;
+    background-color: var(--color-blueE);
+    padding: 10px 10px 10px 25px;
+    overflow-y: auto;
+}
+
+.log-content li {
+    font-size: 1.3rem;
+    color: var(--color-blue);
+    list-style: disc;
+    padding: 5px;
+}
+
+.push-text {
+    padding: 10px;
+    font-family: 'Pretendard';
+    font-size: 15px;
+}
+
+
+/* 요소옵션 추가 240216 김하영 */
+.optionBox {
+    width: 100%;
+    height: 100%;
+    border-radius: 5px;
+    padding: 2rem;
+    background-color: #f9f9f9;
+
+}
+
+.optionBoxText {
+    font-size: 1.5rem;
+    color: #213f99;
+    font-family: 'Pretendard';
+    font-weight: 600;
+}
+
+.selectBoxArea select {
+    border: 0px;
+    background-color: white;
+}
+
+.optionBox {
+    font-size: 1.5rem;
+}
+
+.optionSubText {
+    text-align: center;
+}
+
+.optionSubBox {
+    background-color: white;
+    padding: 10px;
+    border-radius: 5px;
+    overflow-y: scroll;
+    height: 225px;
+}
+
+.optionSubBox li {
+    margin-bottom: 10px;
+}
+
+.cyberpunk-checkbox {
+    appearance: none;
+    width: 20px;
+    height: 20px;
+    border: 2px solid var(--color-blue);
+    border-radius: 5px;
+    background-color: transparent;
+    display: inline-block;
+    position: relative;
+    margin-right: 10px;
+    cursor: pointer;
+}
+
+.cyberpunk-checkbox:before {
+    content: "";
+    background-color: var(--color-blue);
+    display: block;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%) scale(0);
+    width: 10px;
+    height: 10px;
+    border-radius: 3px;
+    transition: all 0.3s ease-in-out;
+}
+
+.cyberpunk-checkbox:checked:before {
+    transform: translate(-50%, -50%) scale(1);
+}(파일 끝에 줄바꿈 문자 없음)
 
client/resources/file/stdWrdFile.xlsx (Binary) (added)
+++ client/resources/file/stdWrdFile.xlsx
Binary file is not shown
 
client/resources/font/GmarketSansBold.woff (Binary) (added)
+++ client/resources/font/GmarketSansBold.woff
Binary file is not shown
 
client/resources/font/GmarketSansLight.woff (Binary) (added)
+++ client/resources/font/GmarketSansLight.woff
Binary file is not shown
 
client/resources/font/GmarketSansMedium.woff (Binary) (added)
+++ client/resources/font/GmarketSansMedium.woff
Binary file is not shown
 
client/resources/font/PretendardVariable.woff2 (Binary) (added)
+++ client/resources/font/PretendardVariable.woff2
Binary file is not shown
 
client/resources/img/chartIcon/bubble_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/bubble_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/clustered_chart_h.png (Binary) (added)
+++ client/resources/img/chartIcon/clustered_chart_h.png
Binary file is not shown
 
client/resources/img/chartIcon/clustered_chart_v.png (Binary) (added)
+++ client/resources/img/chartIcon/clustered_chart_v.png
Binary file is not shown
 
client/resources/img/chartIcon/column_chart_h.png (Binary) (added)
+++ client/resources/img/chartIcon/column_chart_h.png
Binary file is not shown
 
client/resources/img/chartIcon/column_chart_v.png (Binary) (added)
+++ client/resources/img/chartIcon/column_chart_v.png
Binary file is not shown
 
client/resources/img/chartIcon/dounet_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/dounet_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/liin_chart_back.png (Binary) (added)
+++ client/resources/img/chartIcon/liin_chart_back.png
Binary file is not shown
 
client/resources/img/chartIcon/line_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/line_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/mix_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/mix_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/node_line_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/node_line_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/pie_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/pie_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/semicircle_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/semicircle_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/stacked_bar_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/stacked_bar_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/stacked_column_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/stacked_column_chart.png
Binary file is not shown
 
client/resources/img/chartIcon/word_chart.png (Binary) (added)
+++ client/resources/img/chartIcon/word_chart.png
Binary file is not shown
 
client/resources/img/componentIcon/equipment.png (Binary) (added)
+++ client/resources/img/componentIcon/equipment.png
Binary file is not shown
 
client/resources/img/componentIcon/icon1.png (Binary) (added)
+++ client/resources/img/componentIcon/icon1.png
Binary file is not shown
 
client/resources/img/componentIcon/icon2.png (Binary) (added)
+++ client/resources/img/componentIcon/icon2.png
Binary file is not shown
 
client/resources/img/componentIcon/icon3.png (Binary) (added)
+++ client/resources/img/componentIcon/icon3.png
Binary file is not shown
 
client/resources/img/componentIcon/icon4.png (Binary) (added)
+++ client/resources/img/componentIcon/icon4.png
Binary file is not shown
 
client/resources/img/componentIcon/icon5.png (Binary) (added)
+++ client/resources/img/componentIcon/icon5.png
Binary file is not shown
 
client/resources/img/delete.png (Binary) (added)
+++ client/resources/img/delete.png
Binary file is not shown
 
client/resources/img/guideImg/align.png (Binary) (added)
+++ client/resources/img/guideImg/align.png
Binary file is not shown
 
client/resources/img/guideImg/horizontal.png (Binary) (added)
+++ client/resources/img/guideImg/horizontal.png
Binary file is not shown
 
client/resources/img/guideImg/justify.png (Binary) (added)
+++ client/resources/img/guideImg/justify.png
Binary file is not shown
 
client/resources/img/guideImg/vertical.png (Binary) (added)
+++ client/resources/img/guideImg/vertical.png
Binary file is not shown
 
client/resources/img/icon/1.png (Binary) (added)
+++ client/resources/img/icon/1.png
Binary file is not shown
 
client/resources/img/icon/all.png (Binary) (added)
+++ client/resources/img/icon/all.png
Binary file is not shown
 
client/resources/img/icon/bottom.png (Binary) (added)
+++ client/resources/img/icon/bottom.png
Binary file is not shown
 
client/resources/img/icon/dashed.png (Binary) (added)
+++ client/resources/img/icon/dashed.png
Binary file is not shown
 
client/resources/img/icon/dobble.png (Binary) (added)
+++ client/resources/img/icon/dobble.png
Binary file is not shown
 
client/resources/img/icon/dotted.png (Binary) (added)
+++ client/resources/img/icon/dotted.png
Binary file is not shown
 
client/resources/img/icon/hor_a.png (Binary) (added)
+++ client/resources/img/icon/hor_a.png
Binary file is not shown
 
client/resources/img/icon/hor_b.png (Binary) (added)
+++ client/resources/img/icon/hor_b.png
Binary file is not shown
 
client/resources/img/icon/hwp.png (Binary) (added)
+++ client/resources/img/icon/hwp.png
Binary file is not shown
 
client/resources/img/icon/iconCategory.png (Binary) (added)
+++ client/resources/img/icon/iconCategory.png
Binary file is not shown
 
client/resources/img/icon/img.png (Binary) (added)
+++ client/resources/img/icon/img.png
Binary file is not shown
 
client/resources/img/icon/lb.png (Binary) (added)
+++ client/resources/img/icon/lb.png
Binary file is not shown
 
client/resources/img/icon/left.png (Binary) (added)
+++ client/resources/img/icon/left.png
Binary file is not shown
 
client/resources/img/icon/lt.png (Binary) (added)
+++ client/resources/img/icon/lt.png
Binary file is not shown
 
client/resources/img/icon/pdf.png (Binary) (added)
+++ client/resources/img/icon/pdf.png
Binary file is not shown
 
client/resources/img/icon/ppt.png (Binary) (added)
+++ client/resources/img/icon/ppt.png
Binary file is not shown
 
client/resources/img/icon/rb.png (Binary) (added)
+++ client/resources/img/icon/rb.png
Binary file is not shown
 
client/resources/img/icon/right.png (Binary) (added)
+++ client/resources/img/icon/right.png
Binary file is not shown
 
client/resources/img/icon/rt.png (Binary) (added)
+++ client/resources/img/icon/rt.png
Binary file is not shown
 
client/resources/img/icon/solid.png (Binary) (added)
+++ client/resources/img/icon/solid.png
Binary file is not shown
 
client/resources/img/icon/top.png (Binary) (added)
+++ client/resources/img/icon/top.png
Binary file is not shown
 
client/resources/img/icon/txt.png (Binary) (added)
+++ client/resources/img/icon/txt.png
Binary file is not shown
 
client/resources/img/icon/ver_a.png (Binary) (added)
+++ client/resources/img/icon/ver_a.png
Binary file is not shown
 
client/resources/img/icon/ver_b.png (Binary) (added)
+++ client/resources/img/icon/ver_b.png
Binary file is not shown
 
client/resources/img/icon/xls.png (Binary) (added)
+++ client/resources/img/icon/xls.png
Binary file is not shown
 
client/resources/img/loading_mini_icon.png (Binary) (added)
+++ client/resources/img/loading_mini_icon.png
Binary file is not shown
 
client/resources/img/logo.png (Binary) (added)
+++ client/resources/img/logo.png
Binary file is not shown
 
client/resources/img/logo_s.png (Binary) (added)
+++ client/resources/img/logo_s.png
Binary file is not shown
 
client/resources/img/logo_w.png (Binary) (added)
+++ client/resources/img/logo_w.png
Binary file is not shown
 
client/resources/img/opacity.png (Binary) (added)
+++ client/resources/img/opacity.png
Binary file is not shown
 
client/resources/img/option.png (Binary) (added)
+++ client/resources/img/option.png
Binary file is not shown
 
client/resources/vue-flow/controls_latest.css (added)
+++ client/resources/vue-flow/controls_latest.css
@@ -0,0 +1,37 @@
+.vue-flow__controls {
+  box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.08);
+}
+
+.vue-flow__controls-button {
+  background: #fefefe;
+  border: none;
+  border-bottom: 1px solid #eee;
+  box-sizing: content-box;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 16px;
+  height: 16px;
+  cursor: pointer;
+  user-select: none;
+  padding: 5px;
+}
+
+.vue-flow__controls-button svg {
+  width: 100%;
+  max-width: 12px;
+  max-height: 12px;
+}
+
+.vue-flow__controls-button:hover {
+  background: #f4f4f4;
+}
+
+
+.vue-flow__controls-button:disabled {
+  pointer-events: none;
+}
+
+.vue-flow__controls-button:disabled svg {
+  fill-opacity: 0.4;
+}
 
client/resources/vue-flow/minimap_latest.css (added)
+++ client/resources/vue-flow/minimap_latest.css
@@ -0,0 +1,15 @@
+.vue-flow__minimap {
+  background-color: #fff;
+}
+
+.vue-flow__minimap.pannable {
+  cursor: grab;
+}
+
+.vue-flow__minimap.dragging {
+  cursor: grabbing;
+}
+
+.vue-flow__minimap-mask.pannable {
+  cursor: grab;
+}
 
client/resources/vue-flow/node-resizer_latest.css (added)
+++ client/resources/vue-flow/node-resizer_latest.css
@@ -0,0 +1,103 @@
+.vue-flow__resize-control {
+  position: absolute;
+}
+
+.vue-flow__resize-control.left,
+.vue-flow__resize-control.right {
+  cursor: ew-resize;
+}
+
+.vue-flow__resize-control.top,
+.vue-flow__resize-control.bottom {
+  cursor: ns-resize;
+}
+
+.vue-flow__resize-control.top.left,
+.vue-flow__resize-control.bottom.right {
+  cursor: nwse-resize;
+}
+
+.vue-flow__resize-control.bottom.left,
+.vue-flow__resize-control.top.right {
+  cursor: nesw-resize;
+}
+
+/* handle styles */
+.vue-flow__resize-control.handle {
+  width: 4px;
+  height: 4px;
+  border: 1px solid #fff;
+  border-radius: 1px;
+  background-color: #3367d9;
+  transform: translate(-50%, -50%);
+}
+
+.vue-flow__resize-control.handle.left {
+  left: 0;
+  top: 50%;
+}
+.vue-flow__resize-control.handle.right {
+  left: 100%;
+  top: 50%;
+}
+.vue-flow__resize-control.handle.top {
+  left: 50%;
+  top: 0;
+}
+.vue-flow__resize-control.handle.bottom {
+  left: 50%;
+  top: 100%;
+}
+.vue-flow__resize-control.handle.top.left {
+  left: 0;
+}
+.vue-flow__resize-control.handle.bottom.left {
+  left: 0;
+}
+.vue-flow__resize-control.handle.top.right {
+  left: 100%;
+}
+.vue-flow__resize-control.handle.bottom.right {
+  left: 100%;
+}
+
+/* line styles */
+.vue-flow__resize-control.line {
+  border-color: #3367d9;
+  border-width: 0;
+  border-style: solid;
+}
+
+.vue-flow__resize-control.line.left,
+.vue-flow__resize-control.line.right {
+  width: 1px;
+  transform: translate(-50%, 0);
+  top: 0;
+  height: 100%;
+}
+
+.vue-flow__resize-control.line.left {
+  left: 0;
+  border-left-width: 1px;
+}
+.vue-flow__resize-control.line.right {
+  left: 100%;
+  border-right-width: 1px;
+}
+
+.vue-flow__resize-control.line.top,
+.vue-flow__resize-control.line.bottom {
+  height: 1px;
+  transform: translate(0, -50%);
+  left: 0;
+  width: 100%;
+}
+
+.vue-flow__resize-control.line.top {
+  top: 0;
+  border-top-width: 1px;
+}
+.vue-flow__resize-control.line.bottom {
+  border-bottom-width: 1px;
+  top: 100%;
+}
 
client/resources/vue-flow/style.css (added)
+++ client/resources/vue-flow/style.css
@@ -0,0 +1,264 @@
+.vue-flow {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  z-index: 0;
+}
+
+.vue-flow__container {
+  position: absolute;
+  height: 100%;
+  width: 100%;
+  left: 0;
+  top: 0;
+}
+
+.vue-flow__pane {
+  z-index: 1;
+}
+
+.vue-flow__pane.draggable {
+     cursor: grab;
+   }
+
+.vue-flow__pane.dragging {
+     cursor: grabbing;
+   }
+
+.vue-flow__pane.selection {
+     cursor: pointer;
+   }
+
+.vue-flow__transformationpane {
+  transform-origin: 0 0;
+  z-index: 2;
+  pointer-events: none;
+}
+
+.vue-flow__viewport {
+  z-index: 4;
+  overflow: clip;
+}
+
+.vue-flow__selection {
+  z-index: 6;
+}
+
+.vue-flow__edge-labels {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  pointer-events: none;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+          user-select: none;
+}
+
+.vue-flow__nodesselection-rect:focus,
+.vue-flow__nodesselection-rect:focus-visible {
+  outline: none;
+}
+
+.vue-flow .vue-flow__edges {
+  pointer-events: none;
+  overflow: visible;
+}
+
+.vue-flow__edge-path,
+.vue-flow__connection-path {
+  stroke: #b1b1b7;
+  stroke-width: 1;
+  fill: none;
+}
+
+.vue-flow__edge {
+  pointer-events: visibleStroke;
+  cursor: pointer;
+}
+
+.vue-flow__edge.animated path {
+     stroke-dasharray: 5;
+     animation: dashdraw 0.5s linear infinite;
+   }
+
+.vue-flow__edge.animated path.vue-flow__edge-interaction {
+     stroke-dasharray: none;
+     animation: none;
+   }
+
+.vue-flow__edge.inactive {
+     pointer-events: none;
+   }
+
+.vue-flow__edge.selected,
+  .vue-flow__edge:focus,
+  .vue-flow__edge:focus-visible {
+     outline: none;
+   }
+
+.vue-flow__edge.selected .vue-flow__edge-path,
+  .vue-flow__edge:focus .vue-flow__edge-path,
+  .vue-flow__edge:focus-visible .vue-flow__edge-path {
+     stroke: #555;
+   }
+
+.vue-flow__edge-textwrapper {
+     pointer-events: all;
+   }
+
+.vue-flow__edge-textbg {
+     fill: white;
+   }
+
+.vue-flow__edge-text {
+    pointer-events: none;
+    -webkit-user-select: none;
+       -moz-user-select: none;
+            user-select: none;
+  }
+
+.vue-flow__connection {
+  pointer-events: none;
+}
+
+.vue-flow__connection .animated {
+     stroke-dasharray: 5;
+     animation: dashdraw 0.5s linear infinite;
+   }
+
+.vue-flow__connectionline {
+  z-index: 1001;
+}
+
+.vue-flow__nodes {
+  pointer-events: none;
+  transform-origin: 0 0;
+}
+
+.vue-flow__node-default,
+.vue-flow__node-input,
+.vue-flow__node-output {
+  border-width: 1px;
+  border-style: solid;
+  border-color: #bbb;
+}
+
+.vue-flow__node-default.selected,
+  .vue-flow__node-default:focus,
+  .vue-flow__node-default:focus-visible,
+  .vue-flow__node-input.selected,
+  .vue-flow__node-input:focus,
+  .vue-flow__node-input:focus-visible,
+  .vue-flow__node-output.selected,
+  .vue-flow__node-output:focus,
+  .vue-flow__node-output:focus-visible {
+     outline: none;
+     border: 1px solid #555;
+   }
+
+.vue-flow__node {
+  position: absolute;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+          user-select: none;
+  pointer-events: all;
+  transform-origin: 0 0;
+  box-sizing: border-box;
+  cursor: grab;
+}
+
+.vue-flow__node.dragging {
+     cursor: grabbing;
+   }
+
+.vue-flow__nodesselection {
+  z-index: 3;
+  transform-origin: left top;
+  pointer-events: none;
+}
+
+.vue-flow__nodesselection-rect {
+     position: absolute;
+     pointer-events: all;
+     cursor: grab;
+   }
+
+.vue-flow__nodesselection-rect.dragging {
+          cursor: grabbing;
+        }
+
+.vue-flow__handle {
+  position: absolute;
+  pointer-events: none;
+  min-width: 5px;
+  min-height: 5px;
+}
+
+.vue-flow__handle.connectable {
+     pointer-events: all;
+     cursor: crosshair;
+   }
+
+.vue-flow__handle-bottom {
+     top: auto;
+     left: 50%;
+     bottom: -4px;
+     transform: translate(-50%, 0);
+   }
+
+.vue-flow__handle-top {
+     left: 50%;
+     top: -4px;
+     transform: translate(-50%, 0);
+   }
+
+.vue-flow__handle-left {
+     top: 50%;
+     left: -4px;
+     transform: translate(0, -50%);
+   }
+
+.vue-flow__handle-right {
+     right: -4px;
+     top: 50%;
+     transform: translate(0, -50%);
+   }
+
+.vue-flow__edgeupdater {
+  cursor: move;
+  pointer-events: all;
+}
+
+.vue-flow__panel {
+  position: absolute;
+  z-index: 5;
+  margin: 15px;
+}
+
+.vue-flow__panel.top {
+     top: 0;
+   }
+
+.vue-flow__panel.bottom {
+     bottom: 0;
+   }
+
+.vue-flow__panel.left {
+     left: 0;
+   }
+
+.vue-flow__panel.right {
+     right: 0;
+   }
+
+.vue-flow__panel.center {
+     left: 50%;
+     transform: translateX(-50%);
+   }
+
+@keyframes dashdraw {
+  from {
+    stroke-dashoffset: 10;
+  }
+}
 
client/resources/vue-flow/theme-default.css (added)
+++ client/resources/vue-flow/theme-default.css
@@ -0,0 +1,130 @@
+:root {
+  --vf-node-bg: #fff;
+  --vf-node-text: #222;
+  --vf-connection-path:  #b1b1b7;
+  --vf-handle: #555;
+}
+
+.vue-flow__edge.updating .vue-flow__edge-path {
+      stroke: #777;
+    }
+
+.vue-flow__edge-text {
+  font-size: 10px;
+}
+
+.vue-flow__edge-textbg {
+  fill: #fff;
+}
+
+.vue-flow__connection-path {
+  stroke: var(--vf-connection-path);
+}
+
+.vue-flow__node {
+  cursor: grab;
+}
+
+.vue-flow__node.selectable:focus,
+  .vue-flow__node.selectable:focus-visible {
+     outline: none;
+   }
+
+.vue-flow__node-default,
+.vue-flow__node-input,
+.vue-flow__node-output {
+  padding: 10px;
+  border-radius: 3px;
+  width: 150px;
+  font-size: 12px;
+  text-align: center;
+  border-width: 1px;
+  border-style: solid;
+  color: var(--vf-node-text);
+  background-color: var(--vf-node-bg);
+  border-color: var(--vf-node-color);
+}
+
+.vue-flow__node-default.selected,
+  .vue-flow__node-default.selected:hover,
+  .vue-flow__node-input.selected,
+  .vue-flow__node-input.selected:hover,
+  .vue-flow__node-output.selected,
+  .vue-flow__node-output.selected:hover {
+     box-shadow: 0 0 0 0.5px var(--vf-box-shadow);
+   }
+
+.vue-flow__node-default .vue-flow__handle, .vue-flow__node-input .vue-flow__handle, .vue-flow__node-output .vue-flow__handle {
+    background: var(--vf-handle);
+  }
+
+.vue-flow__node-default.selectable:hover, .vue-flow__node-input.selectable:hover, .vue-flow__node-output.selectable:hover {
+    box-shadow: 0 1px 4px 1px rgba(0, 0, 0, 0.08);
+  }
+
+.vue-flow__node-input {
+  --vf-node-color: var(--vf-node-color, #0041d0);
+  --vf-handle: var(--vf-node-color, #0041d0);
+  --vf-box-shadow: var(--vf-node-color, #0041d0);
+
+  background: var(--vf-node-bg);
+  border-color: var(--vf-node-color, #0041d0);
+}
+
+.vue-flow__node-input.selected,
+  .vue-flow__node-input:focus,
+  .vue-flow__node-input:focus-visible {
+     outline: none;
+     border: 1px solid var(--vf-node-color, #0041d0);
+   }
+
+.vue-flow__node-default {
+  --vf-handle: var(--vf-node-color, #1a192b);
+  --vf-box-shadow: var(--vf-node-color, #1a192b);
+
+  background: var(--vf-node-bg);
+  border-color: var(--vf-node-color, #1a192b);
+}
+
+.vue-flow__node-default.selected,
+  .vue-flow__node-default:focus,
+  .vue-flow__node-default:focus-visible {
+     outline: none;
+     border: 1px solid var(--vf-node-color, #1a192b);
+   }
+
+.vue-flow__node-output {
+  --vf-handle: var(--vf-node-color, #ff0072);
+  --vf-box-shadow: var(--vf-node-color, #ff0072);
+
+  background: var(--vf-node-bg);
+  border-color: var(--vf-node-color, #ff0072);
+}
+
+.vue-flow__node-output.selected,
+  .vue-flow__node-output:focus,
+  .vue-flow__node-output:focus-visible {
+     outline: none;
+     border: 1px solid var(--vf-node-color, #ff0072);
+   }
+
+.vue-flow__nodesselection-rect,
+.vue-flow__selection {
+  background: rgba(0, 89, 220, 0.08);
+  border: 1px dotted rgba(0, 89, 220, 0.8);
+}
+
+.vue-flow__nodesselection-rect:focus,
+  .vue-flow__nodesselection-rect:focus-visible,
+  .vue-flow__selection:focus,
+  .vue-flow__selection:focus-visible {
+     outline: none;
+   }
+
+.vue-flow__handle {
+  width: 6px;
+  height: 6px;
+  background: var(--vf-handle);
+  border: 1px solid #fff;
+  border-radius: 100%;
+}
 
client/views/common/commonPlugin.js (added)
+++ client/views/common/commonPlugin.js
@@ -0,0 +1,372 @@
+/**
+ *
+ * 공통 처리 플러그인
+ */
+
+
+function prefixZero(number, length) {
+  var zero = '';
+  number = number.toString();
+
+  if (number.length < length) {
+    for (let i = 0; i < length - number.length; i++) {
+      zero += '0';
+    }
+  }
+  return zero + number;
+}
+
+//////////////////////////////////////////////////////////////////
+
+import axios from 'axios';
+import { mdiMagnify, mdiFolder, mdiFolderOpen, mdiTable, mdiPlay, mdiKeyVariant, mdiTrashCan } from '@mdi/js';
+import moment from 'moment';
+import Vue from "vue";
+
+export default {
+  install(Vue) {
+
+    let alertRef = {};
+    let commonXios = {};
+    let defaultObject = {};
+
+    Vue.config.globalProperties.$setAlertRef = function (ref) {
+      alertRef = ref;
+    }
+
+    //날짜 비교 함수
+    Vue.config.globalProperties.$getSum = function (prevDate, currentDate) {
+      alert(prevDate)
+      alert(currentDate)
+    }
+
+    // 모달 호출
+    Vue.config.globalProperties.$showAlert = function (title, message) {
+      alertRef.setTitle(title);
+      alertRef.setMessage(message);
+      alertRef.showModal();
+    }
+
+    // confirm 창 호출
+    Vue.config.globalProperties.$showConfirm = async function (title, message) {
+
+      alertRef.setTitle(title);
+      alertRef.setMessage(message);
+      const resultData = await alertRef.showConfirm();
+      return resultData;
+    }
+
+    // confirm 창 호출
+    Vue.config.globalProperties.$showRadioConfirm = async function (title, message) {
+
+      alertRef.setTitle(title);
+      alertRef.setMessage(message);
+      const resultData = await alertRef.showRadioConfirm();
+      return resultData;
+    }
+
+    // 공통코드 호출
+    Vue.config.globalProperties.$getCommonCode = async function (GroupCode) {
+      const promise = new Promise((resolve, reject) => {
+        axios({
+          url: '/common/getCodeList.json',
+          method: 'post',
+          headers: {
+            'Content-Type': 'application/json; charset=UTF-8'
+          },
+          data: JSON.stringify({ 'groupCode': GroupCode })
+        }).then(function (response) {
+          resolve(response.data.resultData.codeList)
+        }).catch(function (error) {
+          resolve('cancle')
+        });
+      });
+
+      return promise.then(
+        (data) => {
+          return data;
+        }
+      ).catch(function (err) {
+        console.log(err)
+        return [];
+      });
+    }
+
+    // 데이터베이스 타입호출
+    Vue.config.globalProperties.$getDataBaseTypeList = async function () {
+      const promise = new Promise((resolve, reject) => {
+        axios({
+          url: '/common/getDataBaseTypeList.json',
+          method: 'post',
+          headers: {
+            'Content-Type': 'application/json; charset=UTF-8'
+          }
+        }).then(function (response) {
+          resolve(response.data.resultData.DatabaseTypeList)
+        }).catch(function (error) {
+          resolve('cancle')
+        });
+      });
+
+      return promise.then(
+        (data) => {
+          return data;
+        }
+      ).catch(function (err) {
+        console.log(err)
+        return [];
+      });
+    }
+
+    //시간 구하기
+    Vue.config.globalProperties.$getFullTime = function (hour, minute, seconds) {
+      var date = new Date();
+      var h = date.getHours();
+      var m = date.getMinutes();
+      var s = date.getSeconds();
+      if (this.$isEmpty(hour) == false) {
+        h += hour;
+      } if (this.$isEmpty(minute) == false) {
+        m += minute;
+      } if (this.$isEmpty(seconds) == false) {
+        s += seconds;
+      }
+      return prefixZero(h, 2) + ":" + prefixZero(m, 2) + ":" + prefixZero(s, 2);
+    }
+
+    // 빈값체크
+    Vue.config.globalProperties.$isEmpty = function (data) {
+      if (data === undefined || data === null || data === "" || data.length === 0 || (data.constructor == Object && Object.keys(data).length === 0)) {
+        if ((typeof data) === "number") {
+          return false
+        } else {
+          return true;
+        }
+      } else {
+        return false;
+      }
+    }
+
+    // 기본 검색 객체 생성
+    Vue.config.globalProperties.$getDefaultSerchVO = function () {
+      return {
+        searchObjectList: [],
+        order: '',
+        orderASC: true,
+        currentPage: 1,
+        perPage: 10,
+        totalRows: 0
+      }
+    }
+
+    // 기본 검색 객체 생성
+    Vue.config.globalProperties.$getDefaultSerchItem = function (key, type) {
+
+      let value1 = null;
+      let value2 = null;
+      if (type === 'dates') {
+        value1 = moment().add("-1", "M").format('YYYY-MM-DD');
+        value2 = moment().format('YYYY-MM-DD');
+      }
+      return {
+        key: key,
+        value: value1,
+        key2: key,
+        value2: value2,
+        type: type
+      }
+    }
+
+    // 기본 검색 객체 생성
+    Vue.config.globalProperties.$getIconPath = function (icon) {
+      if (icon == null) {
+        icon = 'mdiMagnify';
+      }
+      if (icon == 'mdiFolder') {
+        return mdiFolder;
+      } else if (icon == 'mdiFolder') {
+        return mdiFolderOpen;
+      } else if (icon == 'mdiTable') {
+        return mdiTable;
+      } else if (icon == 'mdiPlay') {
+        return mdiPlay;
+      } else if (icon == 'mdiKeyVariant') {
+        return mdiKeyVariant;
+      } else if (icon == 'mdiTrashCan') {
+        return mdiTrashCan;
+      } else {
+        return mdiMagnify;
+      }
+    }
+
+    // 기본 job 관련 vo 세팅
+    Vue.config.globalProperties.$setDefaultObject = function () {
+      axios({
+        url: '/common/getDefaultObject.json',
+        method: 'post',
+        headers: {
+          'Content-Type': 'application/json; charset=UTF-8'
+        }
+      }).then(function (response) {
+        defaultObject = response.data.resultData;
+      }).catch(function (error) {
+        console.log(error);
+      });
+    }
+
+    // default jobItm 호출
+    Vue.config.globalProperties.$getDefaultJobGroup = function () {
+      return defaultObject;
+    }
+
+    // default jobItm 호출
+    Vue.config.globalProperties.$getDefaultObject = function () {
+      return defaultObject;
+    }
+
+    // 스타일 생성
+    Vue.config.globalProperties.$createStyleSheet = function (stylesheet) {
+
+      // 스타일 생성
+      let result = '';
+
+      // 폰트스타일
+      if (stylesheet.fontStyle != null) {
+
+        // 폰트 옵션
+        result += 'font-size:' + stylesheet.fontStyle.font_size + 'px;';
+        result += 'color : ' + stylesheet.fontStyle.font_color + ';';
+        result += 'font-family:' + stylesheet.fontStyle.font + ';';
+        result += 'text-align: ' + stylesheet.fontStyle.text_align + ';';
+        result += 'vertical-align: ' + stylesheet.fontStyle.vertical_align + ';';
+
+        // 볼드 처리
+        if (stylesheet.fontStyle.bold_at) result += 'font-weight: bold;'
+        // 이탤릭 처리
+        if (stylesheet.fontStyle.italic_at) result += 'font-style: italic;'
+        // 라인 귿기
+        if (stylesheet.fontStyle.underline_at || stylesheet.fontStyle.line_through_at) {
+          result += 'text-decoration:';
+          if (stylesheet.fontStyle.underline_at) result += ' underline';
+          if (stylesheet.fontStyle.line_through_at) result += ' line-through';
+          result += ';';
+        }
+
+      }
+
+      // 보더스타일
+      if (stylesheet.borderStyle != null) {
+        result += 'border-top:' + stylesheet.borderStyle.border_item[0].border_width + 'px;';
+        result += 'border-right:' + stylesheet.borderStyle.border_item[1].border_width + 'px;';
+        result += 'border-bottom:' + stylesheet.borderStyle.border_item[3].border_width + 'px;';
+        result += 'border-left:' + stylesheet.borderStyle.border_item[2].border_width + 'px;';
+        result += 'border-radius:' + stylesheet.borderStyle.border_item[0].border_radius + 'px ';
+
+        result += stylesheet.borderStyle.border_item[1].border_radius + 'px ';
+        result += stylesheet.borderStyle.border_item[3].border_radius + 'px ';
+        result += stylesheet.borderStyle.border_item[2].border_radius + 'px;';
+        result += 'border-style:' + stylesheet.borderStyle.border_style + ';';
+        result += 'border-color:' + stylesheet.borderStyle.border_color + ';';
+      }
+
+      // 백그라운드
+      if (stylesheet.background_style != null) {
+        // 백그라운드 이미지 사용 여부
+        if (stylesheet.background_style.image_at) {
+          result += 'background-image: linear-gradient(rgba(255,255,255, ' + (1 - stylesheet.background_style.opacity / 100) + ' ), rgba(255, 255, 255, ' + (1 - stylesheet.background_style.opacity / 100) + '))';
+          result += ', url("' + stylesheet.background_style.imageUrl + '");';
+          result += 'background-position: center;';
+          result += 'background-repeat: no-repeat;';
+
+          // 채우기 설정
+          if (stylesheet.background_style.imageType) {
+            result += 'background-size: cover;';
+          } else {
+            result += 'background-size: contain;';
+          }
+
+          result += 'background-size: cover;';
+
+        } else {
+          let opacity = Math.floor(stylesheet.background_style.opacity * 2.55).toString(16);
+          if (opacity == '0') {
+            opacity = '00';
+          }
+          result += 'background-color:' + stylesheet.background_style.background_color + opacity + ';';
+        }
+      }
+
+      return result;
+    }
+
+
+
+
+    /**
+     * 아이디 정규식(5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용)
+     */
+    Vue.config.globalProperties.$idCheck = function (data) {
+      let validateId = /^[a-z0-9_-]{5,20}$/;
+      if (validateId.test(data) === true) return true;
+      return false;
+    }
+
+    /**
+     * 비밀번호 정규식(8~16자의 영문 대문자, 소문자, 숫자, 특수문자를 사용)
+     */
+    Vue.config.globalProperties.$pwCheck = function (data) {
+      let validatePw = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@#$%^&+=!]).{8,16}$/;
+      if (validatePw.test(data) === true) return true;
+      return false;
+    }
+
+    /* 이메일 형식 검사*/
+    Vue.config.globalProperties.$email = function (email) {
+      const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+      try {
+        return emailPattern.test(email);
+      } catch (e) {
+        return false;
+      }
+    }
+
+    /**
+     * IPv4 정규식
+     */
+    Vue.config.globalProperties.$ipv4 = function (ip) {
+      let validateIPv4 = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
+      if (validateIPv4.test(ip) === true) return true;
+      return false;
+    }
+
+    /* 3글자 마다 콤마 찍기 (돈) */
+    Vue.config.globalProperties.$comma = function (text) {
+      try {
+        return text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+      } catch (e) {
+        if (text === undefined || text === null || text === "" || text.length === 0) {
+          return "-";
+        } else {
+          return text;
+        }
+      }
+    }
+
+    /**
+     * 사업자번호 정규식(10자리)
+     */
+    Vue.config.globalProperties.$businessNumber = function (data) {
+      let validateBusinessNumber = /^\d{10}$/;
+      if (validateBusinessNumber.test(data) === true) return true;
+      return false;
+    }
+
+    // 날짜 형식 바꾸기 (YYYY-MM-DD HH:MM:SS)
+    Vue.config.globalProperties.$getFullTime = function (date) {
+      return moment(date).format('YYYY-MM-DD HH:mm:ss');
+    }
+    Vue.config.globalProperties.$getFullFileTime = function (date) {
+      return moment(date).format('YYMMDD_HHmmss');
+    }
+  }
+}(파일 끝에 줄바꿈 문자 없음)
 
client/views/common/filters.js (added)
+++ client/views/common/filters.js
@@ -0,0 +1,46 @@
+import moment from 'moment';
+
+const filters = {
+
+    /**
+    * 아이디 정규식(5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용)
+    */
+    date(value) {
+        return moment(value).format('YYYY-MM-DD');
+    },
+
+    /**
+    * 아이디 정규식(5~20자의 영문 소문자, 숫자와 특수기호(_),(-)만 사용)
+    */
+    dateTime(value) {
+        return moment(value).format('YYYY-MM-DD HH:mm:ss');
+    },
+
+    bytesToSize(bytes) {
+
+        var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+
+        if (bytes == 0) return '0 Byte';
+
+        var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+
+        return Math.trunc((bytes / Math.pow(1024, i)) * 10) / 10 + ' ' + sizes[i];
+
+    },
+
+    /**
+    * 파일명 확장자 제거
+    */
+    removeExtension(value) {
+        // 마지막 '.'의 위치
+        const lastIndex = value.lastIndexOf('.');
+
+        // '.'이 없으면 원본 문자열을 반환
+        if (lastIndex === -1) return value;
+
+        // '.' 이전까지의 문자열을 반환
+        return value.substring(0, lastIndex);
+    }
+}
+
+export default filters;(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/DepartmentTree.vue (added)
+++ client/views/component/DepartmentTree.vue
@@ -0,0 +1,134 @@
+<template>
+  <li class="cursor">
+    <div
+      :class="{
+        'tree-container flex align-center': true,
+        selected: node.dept_code === selectedId,
+      }"
+      @click="handleClick"
+    >
+      <p>
+        <svg-icon
+          type="mdi"
+          :width="18"
+          :height="18"
+          :path="arrowPath"
+        ></svg-icon>
+      </p>
+      <p>
+        <svg-icon
+          type="mdi"
+          :width="18"
+          :height="18"
+          :path="notePath"
+          :color="'#fbbe28'"
+        ></svg-icon>
+      </p>
+      <p class="node-text">{{ node.dept_nm }}</p>
+    </div>
+    <ul
+      v-if="node.children && node.children.length > 0"
+      class="children-node"
+      :style="{ height: toggleSelect === node.dept_code ? 'auto' : '0' }"
+    >
+      <DepartmentTree
+        v-for="(child, index) in node.children"
+        :node="child"
+        :key="index"
+        @selectedNode="parentSelectedNode"
+      />
+    </ul>
+  </li>
+</template>
+
+<script>
+import axios from "axios";
+import SvgIcon from "@jamescoyle/vue-icon";
+import {
+  mdiNote,
+  mdiChevronRight,
+  mdiChevronDown,
+  mdiNoteOutline,
+} from "@mdi/js";
+
+export default {
+  name: "DepartmentTree",
+  props: {
+    node: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  data() {
+    return {
+      toggleSelect: null,
+      notePath: mdiNote,
+      arrowPath: mdiChevronRight,
+      selectedId: null,
+    };
+  },
+  methods: {
+    handleClick: function () {
+      const vm = this;
+
+      // 마지막으로 선택한 노드만 강조되도록
+      const nodeList = document.querySelectorAll(".tree-container");
+      nodeList.forEach((node) => {
+        node.classList.remove("selected");
+      });
+
+      // 노드 선택 시 하위 노드 표시 및 가리기
+      vm.$emit("selectedNode", vm.node);
+      if (vm.toggleSelect === vm.node.dept_code) {
+        vm.toggleSelect = null;
+        vm.selectedId = null;
+        vm.arrowPath = mdiChevronRight;
+        vm.notePath = mdiNote;
+      } else {
+        if (!vm.node.children || vm.node.children.length === 0) {
+          vm.getChildDepartment();
+        }
+        vm.toggleSelect = vm.node.dept_code;
+        vm.selectedId = vm.node.dept_code;
+        vm.arrowPath = mdiChevronDown;
+        vm.notePath = mdiNoteOutline;
+      }
+    },
+    parentSelectedNode(node) {
+      this.$emit("selectedNode", node);
+    },
+    // 깊이 2 이상일 때 자식 노드 불러오기
+    getChildDepartment: function () {
+      axios
+        .get(`/department/tree/${this.node.dept_code}`)
+        .then((response) => {
+          this.node.children.push(...response.data.resultData.childDepartments);
+        })
+        .catch((error) => {
+          this.$showAlert(
+            "오류",
+            "목록 불러오기 오류, 관리자에게 문의바랍니다."
+          );
+        });
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+  },
+};
+</script>
+
+<style scoped>
+.tree-container {
+  padding: 5px 10px;
+}
+.children-node {
+  padding: 0 0 0 10px;
+  overflow: hidden;
+  transition: max-height 0.5s ease-in-out;
+}
+.node-text {
+  font-size: 1.4rem;
+  margin-left: 5px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/FileTree.vue (added)
+++ client/views/component/FileTree.vue
@@ -0,0 +1,242 @@
+<template>
+  <li @click="handleClick(idx, $event, item)" class="cursor">
+    <div
+      :class="{
+        'tree-container flex align-center': true,
+        selected: item.id === selectItem.id,
+      }"
+    >
+      <p v-if="item.children.length != 0">
+        <svg-icon
+          type="mdi"
+          :width="18"
+          :height="18"
+          :path="arrowPath"
+        ></svg-icon>
+      </p>
+      <p>
+        <svg-icon
+          type="mdi"
+          :width="18"
+          :height="18"
+          :path="folderPath"
+          :color="'#fbbe28'"
+        ></svg-icon>
+      </p>
+      <p class="node-text">{{ item.text }}</p>
+    </div>
+    <ul
+      v-if="item.children"
+      class="children-node"
+      :style="{ height: toggleSelect === idx ? 'auto' : '0' }"
+    >
+      <TreeItem
+        :connection="connection"
+        :selectedNode="parentSelectNode"
+        :closeModal="isCloseModal"
+        :selectItem="selectItem"
+        :parentSelectItem="item"
+        v-for="(child, idx) in item.children"
+        :item="child"
+        :idx="child.id"
+        :key="idx"
+        @selectFolder="emit"
+        @isLoading="handleIsLoading"
+        @selectItem="emitFolder"
+      />
+    </ul>
+  </li>
+</template>
+
+<script>
+import axios from "axios";
+import SvgIcon from "@jamescoyle/vue-icon";
+import {
+  mdiFolder,
+  mdiChevronRight,
+  mdiChevronDown,
+  mdiFileMultiple,
+  mdiFolderOpen,
+} from "@mdi/js";
+export default {
+  props: {
+    item: {
+      type: Object,
+      default: {
+        id: null,
+      },
+    },
+    idx: String,
+    selectedNode: String,
+    connection: {
+      type: Object,
+      default: {},
+    },
+    closeModal: Boolean,
+    isLoading: Boolean,
+    selectedId: String,
+    selectItem: {
+      type: Object,
+      default: {
+        id: null,
+      },
+    },
+    parentSelectItem: {
+      type: Object,
+      default: {
+        id: null,
+      },
+    },
+  },
+  data() {
+    return {
+      toggleSelect: null,
+      clidrunNode: false,
+      folderPath: mdiFolder,
+      arrowPath: mdiChevronRight,
+      filePath: mdiFileMultiple,
+      selectedId: null,
+      // host_code: null,
+      parentSelectNode: null,
+      isCloseModal: false,
+      isLoading: this.isLoading,
+      parentSelectedItem: {},
+    };
+  },
+  methods: {
+    handleClick: function (idx, event, item) {
+      this.$emit("isLoading", true); // 부모 컴포넌트에게 로딩중임을 알림
+      event.stopPropagation();
+      // 클릭한 폴더가 최상위 폴더일 때
+      if (item.id == "/home") {
+        this.parentSelectedItem = item; // 최상위 폴더일 때는 부모 폴더가 없으므로 현재 폴더 정보를 저장
+      }
+      // 이미 열려있는 같은 폴더를 클릭했을 때 (폴더별 열림상태 관리 필요)
+      if (this.toggleSelect === idx) {
+        if (idx === this.connection.path) {
+          // 선택된 폴더가 최상위 폴더일 때
+          if (!item.parent) {
+            this.selectedId = null;
+            this.toggleSelect = null;
+            this.parentSelectNode = null;
+            // this.parentSelectedItem = {};
+            this.parentSelectedItem = item; // 최상위 폴더일 때는 부모 폴더가 없으므로 현재 폴더 정보를 저장
+          } else {
+            this.selectedId = item.parent;
+            this.toggleSelect = item.parent;
+            this.parentSelectNode = item.parent;
+            this.parentSelectedItem = this.parentSelectItem;
+          }
+          this.emit(item.parent);
+          this.arrowPath = mdiChevronRight;
+          this.folderPath = mdiFolder;
+        } else {
+          this.parentSelectNode = idx;
+          if (!item.parent) {
+            this.parentSelectedItem = item; // 최상위 폴더일 때는 부모 폴더가 없으므로 현재 폴더 정보를 저장
+          } else {
+            this.parentSelectedItem = this.parentSelectItem;
+          }
+          if (item.children.length === 0) {
+            this.getChildren(idx, item.children);
+          } else {
+            this.emit(idx);
+          }
+          this.selectedId = this.selectItem.id;
+          this.toggleSelect = idx;
+          this.arrowPath = mdiChevronDown;
+          this.folderPath = mdiFolderOpen;
+        }
+        // 다른 폴더를 클릭했을 때 (열려있지 않은 폴더를 클릭했을 때가 되어야 함)
+      } else {
+        this.parentSelectNode = idx;
+        if (!item.parent) {
+          this.parentSelectedItem = item; // 최상위 폴더일 때는 부모 폴더가 없으므로 현재 폴더 정보를 저장
+        } else {
+          this.parentSelectedItem = this.parentSelectItem;
+        }
+        if (item.children.length === 0) {
+          this.getChildren(idx, item.children);
+        } else {
+          this.emit(idx);
+        }
+        this.selectedId = this.selectItem.id;
+        this.toggleSelect = idx;
+        this.arrowPath = mdiChevronDown;
+        this.folderPath = mdiFolderOpen;
+      }
+      this.emitFolder(item, this.parentSelectItem);
+    },
+
+    emit(path) {
+      this.$emit("selectFolder", path);
+    },
+
+    // 자식 파일 정보 조회
+    getChildren(path, children) {
+      const vm = this;
+      vm.connection.path = path;
+      vm.connection.type = "folder";
+      vm.connection.depth = 1;
+
+      axios
+        .get("/files/list", { params: vm.connection })
+        .then((response) => {
+          let childrenList = response.data.resultData.fileList;
+          childrenList.forEach((item) => {
+            children.push(item);
+          });
+          this.emit(path);
+        })
+        .catch((error) => {});
+    },
+
+    // 로딩 상태 변경
+    handleIsLoading(isLoadingValue) {
+      this.$emit("isLoading", true); // 자식 컴포넌트에게 로딩중임을 알림
+    },
+
+    // 선택 폴더 정보 전달
+    emitFolder(item, parentItem) {
+      this.$emit("selectItem", item, parentItem);
+    },
+  },
+
+  watch: {
+    closeModal: function (newValue) {
+      if (newValue) {
+        this.toggleSelect = null;
+        this.selectedId = null;
+        this.arrowPath = mdiChevronRight;
+        this.folderPath = mdiFolder;
+        this.isCloseModal = newValue;
+      } else {
+        this.isCloseModal = newValue;
+      }
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+  },
+  beforeCreate() {
+    this.$options.components.TreeItem = require("./FileTree.vue").default;
+  },
+};
+</script>
+
+<style scoped>
+.tree-container {
+  padding: 5px 10px;
+}
+
+.children-node {
+  padding: 0 0 0 10px;
+  overflow: hidden;
+  transition: max-height 0.5s ease-in-out;
+}
+
+.node-text {
+  font-size: 1.4rem;
+  margin-left: 5px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/FileTreeModal.vue (added)
+++ client/views/component/FileTreeModal.vue
@@ -0,0 +1,115 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>폴더 선택</h2>
+          <button class="close-btn" @click="closeModal()"><svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon></button>
+        </div>
+        <div>
+          <h2>현재 경로</h2>
+          <p>
+            <span>{{ selectedPath }}</span>
+          </p>
+        </div>
+      </div>
+      <div class="modal-content-monthly">
+        <TreeItem :connection="modalConnection" :closeModal="isCloseModal" v-for="(item, idx) in modalNodes" :item="item" :idx="item.id" :key="idx" @selectFolder="selectFolder" @selectItem="handleSelectItem" />
+      </div>
+      <div class="modal-end flex justify-end">
+        <button class="blue-btn small-btn" v-if="selectType === 'copy'" @click="submit('복사')">복사</button>
+        <button class="blue-btn small-btn" v-else-if="selectType === 'move'" @click="submit('이동')">이동</button>
+        <button class="blue-btn small-btn" v-else-if="selectType === 'choose'" @click="submit('선택')">선택</button>
+        <button class="blue-btn small-btn" v-else-if="selectType === 'update'" @click="submit('수정')">수정</button>
+        <button class="gray-btn small-btn" @click="closeModal()">취소</button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import axios from 'axios';
+import SvgIcon from '@jamescoyle/vue-icon';
+import TreeItem from '../component/FileTree.vue';
+import { mdiClose } from '@mdi/js';
+export default {
+  props: {
+    modalNodes: Object,
+    modalConnection: {
+      type: Object,
+      default: {},
+    },
+    modalOpen: Boolean,
+    selectType: Number,
+  },
+  data() {
+    return {
+      // host_code: null,
+      // parentSelectNode: null,
+      closePath: mdiClose,
+      isModalOpen: false,
+      isCloseModal: false,
+      selectedPath: null,
+      selectItem: null,
+    }
+  },
+  methods: {
+    selectFolder(path) {
+      this.$emit('modalSelectFolder', path)
+      this.modalConnection.path = path;
+      this.selectedPath = path;
+    },
+
+    closeModal() {
+      this.isCloseModal = true;
+      this.isModalOpen = false;
+      this.$emit('closeTreeModal', this.isModalOpen);
+    },
+
+    async submit(type) {
+      this.$emit('modalSubmit', type);
+      this.$emit('modalSelectItem', this.selectItem);
+    },
+
+    handleSelectItem(item, parentItem) {
+      // 현재 선택된 폴더와 클릭한 폴더가 같을 때
+      if (this.selectedPath == item.id) {
+        this.selectItem = parentItem;
+      } else {
+        this.selectItem = item;
+      }
+    },
+
+  },
+  watch: {
+    'modalOpen': function (v) {
+      this.isModalOpen = v;
+      this.isCloseModal = !v;
+    },
+  },
+  computed: {
+
+  },
+  components: {
+    'SvgIcon': SvgIcon,
+    'TreeItem': TreeItem,
+  },
+  mounted() {
+  }
+}
+</script>
+<style scoped>
+.tree-container {
+  padding: 5px 10px;
+}
+
+.children-node {
+  padding: 0 0 0 10px;
+  overflow: hidden;
+  transition: max-height 0.5s ease-in-out;
+}
+
+.node-text {
+  font-size: 1.4rem;
+  margin-left: 5px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/LayoutTree.vue (added)
+++ client/views/component/LayoutTree.vue
@@ -0,0 +1,116 @@
+<template>
+    <li class="cursor">
+        <div :class="{ 'tree-container flex align-center': true, 'selected': selectLayout.layout_nm === splitInfo.layout_nm }" @click.stop="clickLayout(splitInfo)" >
+            <p><svg-icon type="mdi" :width="18" :height="18" :path="arrowPath"></svg-icon></p>           
+            <p><svg-icon type="mdi" :width="18" :height="18" :path="folderPath" :color="'#fbbe28'"></svg-icon></p>
+          
+            <p class="node-text">{{ splitInfo.layout_nm}}</p>
+        </div>
+        <ul v-if="splitInfo.children.length > 0 " class="children-node"  style="height: auto;">
+            <TreeItem  v-for="(node , indx) in splitInfo.children" :splitInfo="node" :selectLayout = 'selectLayout' :key="indx" @changeselectLayout = "changeselectLayout" />
+        </ul>
+
+        <!-- <ul v-if="item.children" class="children-node" :style="{ 'height': toggleSelect === idx ? 'auto' : '0' }">
+            <TreeItem :connection="connection" :selectedNode="parentSelectNode" v-for="(child, idx) in item.children" :item="child" :idx="child.id" 
+                :key="idx" @selectForder="emit"/>
+        </ul> -->
+    </li>
+</template>
+
+<script>
+import axios from 'axios';
+import SvgIcon from '@jamescoyle/vue-icon';
+import {mdiLandPlots, mdiChevronRight, mdiChevronDown, mdiRhombusSplit, mdiFolderOpen } from '@mdi/js';
+export default {
+    props: {      
+        splitInfo:{
+            type: Object,
+        },   
+        selectLayout : {
+            type: Object,
+        }
+    },
+    data() {
+        return {
+            currentSelectLayout : this.selectLayout,
+            toggleSelect: false,
+            clidrunNode: false,
+            folderPath: mdiLandPlots,
+            arrowPath: mdiChevronRight,
+            filePath: mdiRhombusSplit,
+            selectedId: null,
+            // host_code: null,
+            parentSelectNode: null,
+        }
+    },
+    methods: {
+        handleClick: function (idx, event, item) {
+          
+        },
+
+        emit(path){
+            this.$emit('selectForder', path)
+        },
+
+        getChildren(path, children) {
+            const vm = this;
+            vm.connection.path = path
+            vm.connection.type = 'folder'
+            vm.connection.depth = 1
+
+            axios.get('/files/list', {params: vm.connection})
+                .then(response => {
+                    let chilerenList = response.data.resultData.fileList
+                    chilerenList.forEach ( item => {
+                        children.push(item)
+                    })
+                    this.$emit('selectForder', path)
+                }).catch(error => {
+
+                })
+        },
+
+        clickLayout : function(splitInfo){          
+            this.toggleSelect = !this.toggleSelect;
+            this.$emit('changeselectLayout', splitInfo);
+        },
+
+        changeselectLayout : function(layout){           
+            this.$emit('changeselectLayout', layout);
+        },
+    },
+    watch: {
+        selectLayout: function (v) {
+            this.currentSelectLayout = v;
+        },       
+    },
+    computed: {
+
+    },
+    components: {
+        'SvgIcon': SvgIcon
+    },
+    beforeCreate() {
+        this.$options.components.TreeItem = require('./LayoutTree.vue').default;
+    },
+    mounted() {
+    }
+}
+</script>
+
+<style scoped>
+.tree-container {
+    padding: 5px 10px;
+}
+
+.children-node {
+    padding: 0 0 0 10px;
+    overflow: hidden;
+    transition: max-height 0.5s ease-in-out;
+}
+
+.node-text {
+    font-size: 1.4rem;
+    margin-left: 5px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/PageNavigation.vue (added)
+++ client/views/component/PageNavigation.vue
@@ -0,0 +1,49 @@
+<template>
+    <p class="navigate_bar flex20 text-rg"><router-link to="/">Home</router-link><span> > {{ pathName() }}</span></p>
+</template>
+
+<script>
+import { useRoute } from 'vue-router';
+export default {
+    data() {
+        return {
+            //현재 라우터의 정보
+            route: useRoute(),
+
+            // 페이지 경로 목록
+            pageList: [
+                { path: "/", name: "대시보드", },
+                { path: "/fileManagement.page", name: "파일 관리", },
+                { path: "/hostManagement.page", name: "호스트 관리", },
+                { path: "/scheduleManagement.page", name: "작업 스케줄 관리", },
+                { path: "/scheduleLogManagement.page", name: "로그 관리", },
+            ],
+        }
+    },
+    methods: {
+        pathName: function () {
+            for (let i = 0; i < this.pageList.length; i++) {
+                if (this.route.path == this.pageList[i]['path']) {
+                    return this.pageList[i]['name'];
+                }
+            }
+            return "대시보드"
+        },
+
+    },
+    watch: {
+
+    },
+    computed: {
+
+    },
+    components: {
+    },
+}
+</script>
+
+<style scoped>
+.navigate_bar {
+    font-size: 1.3rem;
+}
+</style>
 
client/views/component/PaginationButton.vue (added)
+++ client/views/component/PaginationButton.vue
@@ -0,0 +1,170 @@
+<template lang="html">
+	<div class="component-pagination" id="pagination">
+		<div class="pagination-button-type">
+			<a class="first-page" @click="excute(1)" title="첫 번 째 페이지로 이동"></a>
+			<a class="prev" @click="excute(currentPage - 1)" title="이전 페이지로 이동"></a>
+			<a @click="excute(i)" v-for="i in createRange" :class="{ 'active': currentPage == i }">{{ i }}</a>
+			<a class="next" @click="excute(currentPage + 1)" title="다음 페이지로 이동"></a>
+			<a class="end-page" @click="excute(maxEndPage)" title="마지막 페이지로 이동"></a>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		currentPage: {
+			type: Number,
+			default: 0
+		},
+		perPage: {
+			type: Number,
+			default: 10
+		},
+		totalCount: {
+			type: Number,
+			default: 0
+		},
+		maxRange: {
+			type: Number,
+			default: 5
+		},
+		click: Function
+	},
+	emits: ['update:currentPage'],
+	data: function () {
+		return {
+			//data
+		}
+	},
+	methods: {
+		excute: function (i) {
+			if (i >= 1 && i <= this.maxEndPage) {
+				if (i != this.currentPage) {
+					this.$emit('update:currentPage', i);//부모 currentPage에 선택한 page번호 할당
+					this.click(i);//부모 function 실행
+				} else {
+					return;
+				}
+			} else {
+				alert('이동할 페이지가 없습니다.');
+			}
+		}
+	},
+	computed: {
+		startPage: function () {
+			return Math.floor((this.currentPage - 1) / this.maxRange) * this.maxRange + 1;
+		},
+		endPage: function () {
+			if (this.maxEndPage < this.currentEndPage) {
+				return this.maxEndPage;
+			} else {
+				return this.currentEndPage;
+			}
+		},
+		currentEndPage: function () {
+			return this.maxRange * Math.ceil((this.currentPage / this.maxRange));
+		},
+		maxEndPage: function () {
+			return Math.ceil(this.totalCount / this.perPage);
+		},
+		createRange: function () {
+			var range = [];
+			for (var i = this.startPage; i <= this.endPage; i++) {
+				range.push(i);
+			}
+			if (range.length == 0) {
+					range.push(1);
+			}
+			return range;
+		}
+	},
+	watch: {
+
+	},
+	//beforeCreate: function () {},
+	//created: function () {},
+	//beforeUpdate: function () {},
+	//updated: function () {},
+	mounted: function () {
+	}
+}
+</script>
+
+<style scoped>
+/*PAGINATION NEW START*/
+.component-pagination {
+	padding: 15px 0;
+}
+
+.pagination-button-type {
+	display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.pagination-button-type a {
+	display: inline-block;
+	width: 30px;
+	height: 30px;
+	padding: 5px;
+	text-align: center;
+	line-height: 20px;
+	cursor: pointer;
+}
+
+/* 정우추가 */
+.pagination-button-type a.prev:after {
+	content: "\003C";
+	font-family: 'KoPub_Dotum';
+	font-weight: 900;
+}
+
+.pagination-button-type a.next:after {
+	content: "\003E";
+	font-family: 'KoPub_Dotum';
+	font-weight: 900;
+}
+
+.pagination-button-type a.first-page:after {
+	content: "\003C\003C";
+	font-family: 'KoPub_Dotum';
+	font-weight: 900;
+}
+
+.pagination-button-type a.end-page:after {
+	content: "\003E\003E";
+	font-family: 'KoPub_Dotum';
+	font-weight: 900;
+}
+
+.pagination-button-type a.active {
+	background-color: #213f99;
+	color: #fff;
+	cursor: default !important;
+	border-radius: 50px;
+}
+
+.pagination-button-type a:hover:not(.active) {
+	background-color: #213f99;
+	color: #fff;
+	border-radius: 50px;
+}
+
+.pagination-button-type a:first-child {
+	border-top-left-radius: 0px;
+	border-bottom-left-radius: 0px;
+}
+
+.pagination-button-type a:last-child {
+	border-top-right-radius: 0px;
+	border-bottom-right-radius: 0px;
+}
+
+/*PAGINATION NEW END*/
+
+@media screen and (max-width:479px) {
+	.pagination-button-type a {
+		margin: 3px;
+	}
+}</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/ScheculerCreateComp.vue (added)
+++ client/views/component/ScheculerCreateComp.vue
@@ -0,0 +1,885 @@
+<template>
+  <div class="table-zone">
+    <table class="form-table">
+      <colgroup>
+        <col style="width: 10%" />
+        <col style="width: 40%" />
+      </colgroup>
+      <tbody>
+        <tr>
+          <th>제목</th>
+          <td>
+            <input
+              type="text"
+              name=""
+              id="inputid"
+              class="full-input"
+              v-model="title"
+            />
+          </td>
+        </tr>
+        <tr>
+          <th>내용</th>
+          <td><textarea rows="4" v-model="dc"></textarea></td>
+        </tr>
+        <tr>
+          <th>작업타입</th>
+          <td>
+            <div class="input-container flex">
+              <label class="radio-label">
+                <input
+                  type="radio"
+                  name="radio2"
+                  :value="false"
+                  class="custom-radiobox"
+                  v-model="isUsehighClassOption"
+                />
+                <span>일반 방식</span>
+              </label>
+              <label class="radio-label">
+                <input
+                  type="radio"
+                  name="radio2"
+                  :value="true"
+                  class="custom-radiobox"
+                  v-model="isUsehighClassOption"
+                />
+                <span>커스텀 방식</span>
+              </label>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <th>작업타입 설정</th>
+          <td>
+            <div class="input-container flex">
+              <label class="radio-label">
+                <input
+                  type="radio"
+                  name="Cyclical"
+                  :value="true"
+                  class="custom-radiobox"
+                  v-model="isCyclical"
+                />
+                <span>반복작업(반복주기 설정)</span>
+              </label>
+            </div>
+          </td>
+        </tr>
+        <tr>
+          <th>반복주기 설정</th>
+          <td>
+            <div
+              class="flex justify-start"
+              v-show="isUsehighClassOption == false"
+            >
+              <select
+                name="cycle"
+                v-model="cronItem"
+                :disabled="isUsehighClassOption == true"
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option
+                  v-for="(value, key, i) in cronItems"
+                  :key="i"
+                  :value="key"
+                >
+                  매 {{ value.title }}
+                </option>
+                <option value="ALL">전체</option>
+              </select>
+              <select
+                name="year"
+                v-model="cronMap.YEAR"
+                v-if="isUsehighClassOption == false"
+                v-show="cronItem == 'YEAR' || cronItem == 'ALL'"
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option value="*">매년</option>
+                <option
+                  v-for="(item, idx) in cronItems.YEAR.values"
+                  :key="idx"
+                  :value="item"
+                >
+                  {{ item }}년
+                </option>
+              </select>
+              <select
+                name="months"
+                v-model="cronMap.MONTHS"
+                v-show="
+                  cronItem == 'MONTHS' ||
+                  cronItem == 'YEAR' ||
+                  cronItem == 'ALL'
+                "
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option value="*">매월</option>
+                <option
+                  v-for="(item, idx) in cronItems.MONTHS.values"
+                  :key="idx"
+                  :value="item"
+                >
+                  {{ item }}월
+                </option>
+              </select>
+              <select
+                name="weeks"
+                v-model="cronMap.WEEKS"
+                v-show="cronItem == 'WEEKS' || cronItem == 'ALL'"
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option
+                  v-for="(item, idx) in cronItems.WEEKS.values"
+                  :key="idx"
+                  :value="idx + ''"
+                >
+                  {{ item }}요일
+                </option>
+                <option value="1,2,3,4,5">평일</option>
+                <option value="0,6">주말(토,일)</option>
+                <option value="?" v-show="cronItem != 'WEEKS'">없음</option>
+              </select>
+              <select
+                name="days"
+                v-model="cronMap.DAYS"
+                v-show="
+                  cronItem == 'DAYS' ||
+                  cronItem == 'MONTHS' ||
+                  cronItem == 'YEAR' ||
+                  cronItem == 'ALL'
+                "
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option value="*" v-show="cronItem == 'DAYS'">매일</option>
+                <option
+                  v-for="(item, idx) in cronItems.DAYS.values"
+                  :key="idx"
+                  :value="item"
+                >
+                  {{ item }}일
+                </option>
+                <option value="L">말일</option>
+              </select>
+              <select
+                name="hours"
+                v-model="cronMap.HOURS"
+                v-show="
+                  cronItem == 'HOURS' ||
+                  cronItem == 'DAYS' ||
+                  cronItem == 'WEEKS' ||
+                  cronItem == 'MONTHS' ||
+                  cronItem == 'YEAR' ||
+                  cronItem == 'ALL'
+                "
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option
+                  v-for="(item, idx) in cronItems.HOURS.values"
+                  :key="idx"
+                  :value="item"
+                >
+                  {{ item }}시
+                </option>
+              </select>
+              <select
+                name="minute"
+                v-model="cronMap.MINUTES"
+                v-show="
+                  cronItem == 'MINUTES' ||
+                  cronItem == 'HOURS' ||
+                  cronItem == 'DAYS' ||
+                  cronItem == 'WEEKS' ||
+                  cronItem == 'MONTHS' ||
+                  cronItem == 'YEAR' ||
+                  cronItem == 'ALL'
+                "
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option
+                  v-for="(item, idx) in cronItems.MINUTES.values"
+                  :key="idx"
+                  :value="item"
+                >
+                  {{ item }}분
+                </option>
+              </select>
+              <select
+                name="seconds"
+                v-model="cronMap.SECONDS"
+                v-show="
+                  cronItem == 'SECONDS' ||
+                  'MINUTES' ||
+                  cronItem == 'HOURS' ||
+                  cronItem == 'DAYS' ||
+                  cronItem == 'WEEKS' ||
+                  cronItem == 'MONTHS' ||
+                  cronItem == 'YEAR' ||
+                  cronItem == 'ALL'
+                "
+                style="margin-left: 5px; margin-bottom: 5px"
+              >
+                <option
+                  v-for="(item, idx) in cronItems.SECONDS.values"
+                  :key="idx"
+                  :value="item"
+                >
+                  {{ item }}초
+                </option>
+              </select>
+            </div>
+            <div class="flex justify-start" v-show="isUsehighClassOption">
+              <select
+                name="cycle"
+                v-model="cronItem"
+                :disabled="isUsehighClassOption == true"
+                style="width: 100px; margin-left: 5px; margin-bottom: 5px"
+              >
+                <option
+                  v-for="(value, key, i) in cronItems"
+                  :key="i"
+                  :value="key"
+                >
+                  매 {{ value.title }}
+                </option>
+                <option value="ALL">전체 주기</option>
+              </select>
+              <label for="year">
+                <input
+                  type="text"
+                  id="year"
+                  name="year"
+                  v-model="cronMap.YEAR"
+                  style="margin-bottom: 5px; width: 100px"
+                />
+              </label>
+              <label for="months">
+                <input
+                  type="text"
+                  id="months"
+                  name="months"
+                  v-model="cronMap.MONTHS"
+                  style="width: 100px; margin-bottom: 5px"
+                />
+              </label>
+              <label for="weeks">
+                <input
+                  type="text"
+                  id="weeks"
+                  name="weeks"
+                  v-model="cronMap.WEEKS"
+                  style="width: 100px; margin-bottom: 5px"
+                />
+              </label>
+              <label for="days">
+                <input
+                  type="text"
+                  id="days"
+                  name="days"
+                  v-model="cronMap.DAYS"
+                  style="width: 100px; margin-bottom: 5px"
+                />
+              </label>
+              <label for="hours">
+                <input
+                  type="text"
+                  id="hours"
+                  name="hours"
+                  v-model="cronMap.HOURS"
+                  style="width: 100px; margin-bottom: 5px"
+                />
+              </label>
+              <label for="minutes">
+                <input
+                  type="text"
+                  id="minutes"
+                  name="minutes"
+                  v-model="cronMap.MINUTES"
+                  style="width: 100px; margin-bottom: 5px"
+                />
+              </label>
+              <label for="seconds">
+                <input
+                  type="text"
+                  id="seconds"
+                  name="seconds"
+                  v-model="cronMap.SECONDS"
+                  style="width: 100px; margin-bottom: 5px"
+                />
+              </label>
+            </div>
+          </td>
+        </tr>
+        <tr v-show="isCyclical">
+          <th>작업타입 설정</th>
+          <td>
+            <div class="input-container flex">
+              <p>
+                <b> {{ getCronStr }} </b><br />(표현식:{{ getCron }})
+              </p>
+            </div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</template>
+
+<script>
+//크론 설정값 초기화
+var cronMapInit = {
+  SECONDS: "0",
+  MINUTES: "0",
+  HOURS: "0",
+  DAYS: "*",
+  WEEKS: "?",
+  MONTHS: "*",
+  YEAR: "*",
+};
+
+export default {
+  props: {
+    schedule: {
+      type: Object,
+      default: {},
+    },
+  },
+  data() {
+    return {
+      //고오급 옵션(개발자용)
+      isUsehighClassOption: false,
+
+      //반복작업 여부 : 반복 작업(반복 주기 설정):true, 예약 작업(작업 날짜 설정 - 1회성):false
+      isCyclical: true,
+
+      //Cron 표현식에 필요한 요소's
+      cronItems: {
+        SECONDS: {
+          title: "초",
+          values: [],
+        },
+        MINUTES: {
+          title: "분",
+          values: [],
+        },
+        HOURS: {
+          title: "시",
+          values: [],
+        },
+        DAYS: {
+          title: "일",
+          values: [],
+        },
+        WEEKS: {
+          title: "요일",
+          values: ["일", "월", "화", "수", "목", "금", "토"], //일:0, 월:1, 화:2, 수:3, 목:4, 금:5, 토:6
+        },
+        MONTHS: {
+          title: "월",
+          values: [],
+        },
+        YEAR: {
+          title: "년",
+          values: [],
+        },
+      },
+
+      //Cron 표현식에 필요한 요소 중 선택된 요소
+      cronItem: "ALL",
+
+      //작업명
+      title: "",
+
+      // 설명
+      dc: "",
+
+      //설정된 크론값
+      cronMap: JSON.parse(JSON.stringify(cronMapInit)),
+
+      //크론 표현식 설명 키워드
+      cronKeyWord: {
+        "*": "매",
+        "?": "",
+        L: "마지막",
+        "/": "부터 매",
+        W: "에 가장 가까운 평일", //DAYS(일)만 사용
+        "#": "번째 주", //WEEK(요일)만 사용
+      },
+    };
+  },
+  computed: {
+    getCron() {
+      var c = this.cronMap;
+      return (
+        c.SECONDS +
+        " " +
+        c.MINUTES +
+        " " +
+        c.HOURS +
+        " " +
+        c.DAYS +
+        " " +
+        c.WEEKS +
+        " " +
+        c.MONTHS +
+        " " +
+        c.YEAR
+      );
+    },
+
+    getCronStr() {
+      var cronStr = "";
+      cronStr += this.getCronWord("YEAR", this.cronMap.YEAR);
+      cronStr += "  " + this.getCronWord("MONTHS", this.cronMap.MONTHS);
+      if (this.cronItem != "DAYS") {
+        cronStr += "  " + this.getCronWord("WEEKS", this.cronMap.WEEKS);
+      }
+      if (this.cronItem != "WEEKS") {
+        cronStr += "  " + this.getCronWord("DAYS", this.cronMap.DAYS);
+      }
+      cronStr += "  " + this.getCronWord("HOURS", this.cronMap.HOURS);
+      cronStr += "  " + this.getCronWord("MINUTES", this.cronMap.MINUTES);
+      cronStr += "  " + this.getCronWord("SECONDS", this.cronMap.SECONDS);
+
+      return cronStr;
+    },
+  },
+  mounted() {
+    this.fnSarchCronItemValue(); // 반복 주기 설정을 위한 selectbox 내부 데이터(최초 1회 실행)
+    this.initEvent();
+    this.isUsehighClassOption = false;
+  },
+  methods: {
+    // cronItem value
+    fnSarchCronItemValue() {
+      this.fnValueLoop("SECONDS", 59);
+      this.fnValueLoop("MINUTES", 59);
+      this.fnValueLoop("HOURS", 23);
+      this.fnValueLoop("DAYS", 31);
+      this.fnValueLoop("MONTHS", 12);
+      this.fnValueLoop("YEAR", 5);
+    },
+    fnValueLoop(type, number) {
+      let flag = number + 1;
+      if (type == "YEAR") {
+        let now = new Date();
+        let year = now.getFullYear();
+        for (let i = year; i < year + flag; i++) {
+          this.cronItems.YEAR.values.push(i.toString());
+        }
+      } else {
+        for (let i = 0; i < flag; i++) {
+          this.cronItems[type].values.push(i.toString());
+        }
+      }
+    },
+    // 초기화
+    initEvent() {
+      if (!this.$isEmpty(this.schedule.schdul_id)) {
+        this.cronItem = "ALL";
+        this.title = this.schedule.sj;
+        this.dc = this.schedule.dc;
+        this.isCyclical = this.schedule.cycle_at;
+
+        var tempcron = this.schedule.cron.split(" ");
+        this.cronMap.SECONDS = tempcron[0];
+        this.cronMap.MINUTES = tempcron[1];
+        this.cronMap.HOURS = tempcron[2];
+        this.cronMap.DAYS = tempcron[3];
+        this.cronMap.WEEKS = tempcron[4];
+        this.cronMap.MONTHS = tempcron[5];
+        this.cronMap.YEAR = tempcron[6];
+
+        this.fnJoinCronValue();
+      }
+    },
+    fnJoinCronValue() {
+      for (let type in this.cronItems) {
+        for (let cronItem of this.cronItems[type].values) {
+          if (this.cronMap[type] == cronItem) {
+            this.cronItem = type;
+            break;
+          }
+        }
+      }
+    },
+
+    //개발자 옵션 (비밀)
+    highClassSetting() {
+      if (this.isUsehighClassOption == true) {
+        this.cronMap = JSON.parse(JSON.stringify(cronMapInit));
+        this.isUsehighClassOption = false;
+        this.cronItem = "DAYS";
+      } else {
+        this.isUsehighClassOption = true;
+        this.cronItem = "ALL";
+      }
+    },
+
+    //Cron 표현 단어 해석
+    //cronItem: SECONDS, MINUTES, HOURS, DAYS, WEEKS, MONTHS
+    //value: 값 - ex) 3#2
+    getCronWord(cronItem, value) {
+      if (this.$isEmpty(this.cronItems[cronItem]) == true) {
+        /* 오류 발생 여부 */
+        return "(Error - cronItem)";
+      }
+
+      if (this.$isEmpty(value) == true) {
+        /* 오류 발생 여부 */
+        return "(Error - 값 없음)";
+      }
+
+      //초 or 분 or 시 or 일 or 요일 or 월
+      var unit = this.cronItems[cronItem].title;
+
+      /****** value의 값이 cronKeyWord에 포함되어 있는지 검색 ******/
+      var isFind = false;
+      var findInfo = {
+        key: null, //*, ?, L, /, W, #
+        index: -1, //key를 찾은 위치
+      };
+      for (var key in this.cronKeyWord) {
+        var index = value.indexOf(key);
+        if (index > -1) {
+          isFind = true;
+
+          findInfo.key = key;
+          findInfo.index = index;
+
+          break;
+        }
+      }
+      /****** value의 값이 cronKeyWord에 포함되어 있는지 검색 ******/
+
+      /************* 단어 만들기 *************/
+      var word = "";
+
+      //cronKeyWord의 key(*, ?, L, /, W, #)값 중 value값에 포함된 단어가 있을 때
+      if (isFind == true) {
+        //value == '*' or '?' or 'L'
+        if (this.cronKeyWord[value] != undefined) {
+          word = this.cronKeyWord[value];
+          if (this.cronKeyWord[value] != "") {
+            word += unit;
+          }
+        }
+
+        //value == '/' or 'W' or '#'
+        else {
+          var keyowrd = this.cronKeyWord[findInfo.key];
+          if (key == "/") {
+            var v1 = value.substring(0, findInfo.index);
+            var v2 = value.substring(findInfo.index + 1, value.length);
+
+            //getConvertWeek() : cronItem이 'WEEKS' 일때 -> '숫자 요일' 값을 '문자 요일' 값으로 바꿈
+            v1 = this.getConvertWeek(cronItem, v1);
+            v2 = this.getConvertWeek(cronItem, v2);
+
+            word = v1 + unit + " " + keyowrd + " " + (v2 + unit) + "씩";
+          } else if (key == "W" && cronItem == "DAYS") {
+            var v = value.replace("W");
+            word = v + unit + keyword;
+          } else if (key == "#" && cronItem == "WEEKS") {
+            var v1 = value.substring(0, findInfo.index);
+            var v2 = value.substring(findInfo.index + 1, value.length);
+
+            //getConvertWeek() : cronItem이 'WEEKS' 일때 -> '숫자 요일' 값을 '문자 요일' 값으로 바꿈
+            v2 = this.getConvertWeek(cronItem, v2);
+
+            word = v1 + keyowrd + " " + (v2 + unit);
+          } else {
+            word = "(Error - '" + key + "'의 표현이 잘못됨)";
+          }
+        }
+      }
+
+      //날짜를 나타내는 단어 일 확률이 높음
+      else {
+        //getConvertWeek() : cronItem이 'WEEKS' 일때 -> '숫자 요일' 값을 '문자 요일' 값으로 바꿈
+        value = this.getConvertWeek(cronItem, value);
+        word = value + unit;
+      }
+      /************* 단어 만들기 *************/
+
+      return word;
+    },
+
+    //cronItem이 WEEKS 일때 만, index(value)로 '요일' 찾기
+    //cronItem이 WEEKS가 아닐 때, value값을 그대로 돌려줌
+    getConvertWeek(cronItem, value) {
+      if (cronItem == "WEEKS") {
+        try {
+          if (value.indexOf(",") > -1) {
+            var result = "";
+            var values = value.split(",");
+            for (var i = 0; i < values.length; i++) {
+              //문자 형태의 숫자를 -> 숫자 형태로 바꿈
+              var index = Number(values[i].trim());
+              //'요일'을 가지고 옴
+              result += this.cronItems.WEEKS.values[index];
+              //마지막 전 까지만 ','를 붙여줌
+              if (i < values.length - 1) {
+                result += ", ";
+              }
+            }
+
+            if (result.indexOf("undefined") > -1) {
+              result = "(Error - 요일의 값의 '" + value + "' 표현이 잘못됨)";
+            } else {
+              return result;
+            }
+          } else if (value.indexOf("-") > -1) {
+            var result = "";
+            //'-'가 들어간 위치를 가지고옴
+            var index = value.indexOf("-");
+            //'-' 앞 뒤의 값을 가지고 옴
+            var v1 = value.substring(0, index);
+            var v2 = value.substring(index + 1, value.length);
+
+            //'-' 앞 뒤의 값을 숫자 형태로 바꿔줌
+            v1 = Number(v1.trim());
+            v2 = Number(v2.trim());
+
+            result += this.cronItems.WEEKS.values[v1];
+            result += "-";
+            result += this.cronItems.WEEKS.values[v2];
+
+            if (result.indexOf("undefined") > -1) {
+              result = "(Error - 요일의 값의 '" + value + "' 표현이 잘못됨)";
+            } else {
+              return result;
+            }
+          } else {
+            var result = this.cronItems.WEEKS.values[value];
+
+            if (result.indexOf("undefined") > -1) {
+              result = "(Error - 요일의 값의 '" + value + "' 표현이 잘못됨)";
+            } else {
+              return result;
+            }
+          }
+        } catch (e) {
+          return "(Error - 요일의 값의 '" + value + "' 표현이 잘못됨)";
+        }
+      } else {
+        return value;
+      }
+    },
+  },
+  watch: {
+    schedule(v) {
+      this.initEvent();
+    },
+    cronItem(newValue, oldValue) {
+      //전체 주기 설정시
+      if (newValue == "ALL") {
+        //'월'을 같이 표시해줘야 되기 때문에, '매일'을 뺌
+        /* if (this.cronMap.DAYS == '*' || this.cronMap.DAYS == '?') {
+                  this.cronMap.DAYS = '1';
+              } */
+        //'월', '일'이 같이 표시되기 때문에, '요일'을 기본적으로 [설정안함]
+        /* if (this.cronMap.WEEKS != '?') {
+                  this.cronMap.WEEKS = '?';
+              } */
+      }
+      //'월' 별 주기 설정시
+      else if (newValue == "YEAR") {
+        //'년'을 같이 표시해줘야 되기 때문에, '매일'을 뺌
+
+        if (this.cronMap.MONTH == "*" || this.cronMap.MONTH == "?") {
+          this.cronMap.DAYS = "1";
+        }
+
+        if (this.cronMap.DAYS == "*" || this.cronMap.DAYS == "?") {
+          this.cronMap.DAYS = "1";
+        }
+
+        //혼선을 피하기 위해 '요일'은 [설정안함]
+        this.cronMap.WEEKS = "?";
+      }
+
+      //'월' 별 주기 설정시
+      else if (newValue == "MONTHS") {
+        //'월'을 같이 표시해줘야 되기 때문에, '매일'을 뺌
+        if (this.cronMap.DAYS == "*" || this.cronMap.DAYS == "?") {
+          this.cronMap.DAYS = "1";
+        }
+        //혼선을 피하기 위해 '요일'은 [설정안함]
+        this.cronMap.YEAR = "*";
+        this.cronMap.WEEKS = "?";
+      }
+
+      //'요일' 별 주기 설정시
+      else if (newValue == "WEEKS") {
+        //'월'의 기본 세팅 값을 '매월'로 바꿈, '일'은 [설정안함]
+        this.cronMap.YEAR = "*";
+        this.cronMap.MONTHS = "*";
+        this.cronMap.DAYS = "?";
+
+        //'요일'의 설정 값이 [설정안함]으로 되어 있을 때, -> '0:일요일'로 설정
+        if (this.cronMap.WEEKS == "?") {
+          this.cronMap.WEEKS = "0";
+        }
+      }
+
+      //'일' 별 주기 설정시
+      else if (newValue == "DAYS") {
+        //'월'의 기본 세팅 값을 '매월'로 바꿈, '요일'은 [설정안함]
+        this.cronMap.YEAR = "*";
+        this.cronMap.MONTHS = "*";
+        this.cronMap.WEEKS = "?";
+        this.cronMap.DAYS = "*";
+
+        //'일'의 설정 값이 [설정안함]으로 되어 있을 때, -> '매일'로 설정
+        /* if (this.cronMap.DAYS == '?') {
+                  this.cronMap.DAYS = '*';
+              } */
+      }
+      // '시간' 별 주기 설정 시
+      else if (newValue == "HOURS") {
+        this.cronMap.YEAR = "*";
+        this.cronMap.MONTHS = "*";
+        this.cronMap.WEEKS = "?";
+        this.cronMap.DAYS = "*";
+      }
+      // '분' 별 주기 설정 시
+      else if (newValue == "MINUTES") {
+        this.cronMap.YEAR = "*";
+        this.cronMap.MONTHS = "*";
+        this.cronMap.WEEKS = "?";
+        this.cronMap.DAYS = "*";
+        this.cronMap.HOURS = "*";
+      }
+      // '초' 별 주기 설정 시
+      else if (newValue == "SECONDS") {
+        this.cronMap.YEAR = "*";
+        this.cronMap.MONTHS = "*";
+        this.cronMap.WEEKS = "?";
+        this.cronMap.DAYS = "*";
+        this.cronMap.HOURS = "*";
+        this.cronMap.MINUTES = "*";
+      }
+      // 그 외
+      else {
+        this.cronMap.YEAR = "*";
+        this.cronMap.MONTHS = "*";
+        this.cronMap.WEEKS = "?";
+        this.cronMap.DAYS = "*";
+      }
+    },
+
+    isCyclical(v) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        v
+      );
+    },
+
+    /* 데이터를 부모 컴포넌트로 전달해주기 위함 */
+    title(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    dc(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.SECONDS"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.MINUTES"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.HOURS"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.DAYS"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.WEEKS"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.MONTHS"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+
+    "cronMap.YEAR"(newValue, oldValue) {
+      this.$emit(
+        "get-edit-scheduling",
+        this.title,
+        this.dc,
+        this.getCronStr,
+        this.getCron,
+        this.isCyclical
+      );
+    },
+    /* 데이터를 부모 컴포넌트로 전달해주기 위함 */
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/SplitterLayout.vue (added)
+++ client/views/component/SplitterLayout.vue
@@ -0,0 +1,144 @@
+<template>
+  <Splitter
+    :id="splitInfo['layoutNm']"
+    class="mb-5"
+    :layout="splitInfo['layoutType']"
+    @resize="updateSizes"
+    :resizable="true"
+  >
+    <SplitterPanel
+      :id="splitInfo['children'][0]['layoutNm']"
+      class="flex align-items-center justify-content-center"
+      :class="{
+        active:
+          clickElement == splitInfo['children'][0]['layoutNm'] &&
+          splitInfo['children'][0],
+        'padding-1': splitInfo['children'][0]['componentNm'] != null,
+      }"
+      :size="splitInfo['sizes'][0]"
+    >
+      <!-- <SplitterLayout :is="SplitterLayout" id="SplitterLayout" :clickElement="clickElement" :layoutVal="layoutVal"></SplitterLayout> -->
+      <SplitterLayout
+        v-if="
+          splitInfo['children'][0]['children'] !== null &&
+          splitInfo['children'][0]['children'].length > 0
+        "
+        :is="SplitterLayout"
+        :splitInfo="splitInfo['children'][0]['children'][0]"
+        :clickElement="clickElement"
+        :optionChangeClick="optionChangeClick"
+        :componentOptn="componentOptn"
+        @parentInfo="parentInfo"
+      ></SplitterLayout>
+
+      <!-- <HorizentalThreeData ></HorizentalThreeData> -->
+      <!-- <div style="height:100%; width:100%;"> -->
+      <component
+        v-if="splitInfo['children'][0]['componentNm']"
+        :is="splitInfo['children'][0]['componentNm']"
+        :parent="splitInfo['children'][0]"
+        :optionChangeClick="optionChangeClick"
+        :componentOptn="componentOptn"
+        @parentInfo="parentInfo"
+      >
+      </component>
+      <!-- </div> -->
+    </SplitterPanel>
+    <SplitterPanel
+      v-if="splitInfo['children'][0]['layoutNm'] !== undefined"
+      :id="splitInfo['children'][1]['layoutNm']"
+      class="flex align-items-center justify-content-center"
+      :class="{
+        active:
+          clickElement == splitInfo['children'][1]['layoutNm'] &&
+          splitInfo['children'][1],
+        'padding-1': splitInfo['children'][1]['componentNm'] != null,
+      }"
+      :size="splitInfo['sizes'][1]"
+    >
+      <!-- <SplitterLayout :is="SplitterLayout" id="SplitterLayout" :clickElement="clickElement" :layoutVal="layoutVal"></SplitterLayout> -->
+      <SplitterLayout
+        v-if="
+          splitInfo['children'][0]['children'] !== null &&
+          splitInfo['children'][0]['children'].length > 0
+        "
+        :is="SplitterLayout"
+        :splitInfo="splitInfo['children'][1]['children'][0]"
+        :clickElement="clickElement"
+        :optionChangeClick="optionChangeClick"
+        :componentOptn="componentOptn"
+        @parentInfo="parentInfo"
+      ></SplitterLayout>
+      <component
+        v-if="splitInfo['children'][1]['componentNm']"
+        :is="splitInfo['children'][1]['componentNm']"
+        :parent="splitInfo['children'][1]"
+        :optionChangeClick="optionChangeClick"
+        :componentOptn="componentOptn"
+        @parentInfo="parentInfo"
+      ></component>
+    </SplitterPanel>
+  </Splitter>
+</template>
+<script>
+import HorizentalThreeData from "./elementComponent/HorizentalThreeData.vue";
+import HorizentalTwoData from "./elementComponent/HorizentalTwoData.vue";
+import TableData from "./elementComponent/TableData.vue";
+import ThreeData from "./elementComponent/ThreeData.vue";
+import EquipmentData from "./elementComponent/EquipmentData.vue"; // 2024.02.16 PJH
+
+export default {
+  name: "SplitterLayout",
+  props: {
+    splitInfo: {
+      type: Object,
+    },
+    clickElement: null,
+    optionChangeClick: {
+      type: Function,
+    },
+    componentOptn: {
+      type: Object,
+      required: true,
+    },
+    inputVal: {
+      type: Object,
+      required: true,
+    },
+  },
+  components: {
+    HorizentalThreeData,
+    HorizentalTwoData,
+    TableData,
+    ThreeData,
+    EquipmentData, // 2024.02.16 PJH
+    SplitterLayout: () => import("./SplitterLayout.vue"),
+  },
+  methods: {
+    // newSizes는 사용자가 조정한 후의 panel 크기 비율을 배열로 제공합니다.
+    updateSizes(newSizes) {
+      this.splitInfo["sizes"] = newSizes["sizes"];
+    },
+    parentInfo: function (parentInfo) {
+      this.$emit("parentInfo", parentInfo);
+    },
+  },
+  computed: {
+    isReSize() {
+      return this.$route.path.includes("customSelectOne") ? false : true;
+    },
+  },
+};
+</script>
+<style scoped>
+.active {
+  border: 3px dotted red;
+}
+.padding-1 {
+  padding: 1rem;
+}
+.p-splitter-gutter-handle,
+.p-splitter-gutter {
+  pointer-events: none;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/SplitterLayoutDev.vue (added)
+++ client/views/component/SplitterLayoutDev.vue
@@ -0,0 +1,173 @@
+<template>
+  <Splitter
+    v-if="splitInfo['children'].length <= 0"
+    :id="splitInfo['layout_nm']"
+    class="mb-5"
+    :layout="splitInfo['layout_type']"
+    @resizeend="updateSizes"
+    @Click.stop="clickLayout(splitInfo)"
+    :style="this.$createStyleSheet(splitInfo.styleSheet)"
+  >
+    <SplitterPanel
+      v-if="splitInfo['children'][0] != null"
+      class="flex align-items-center justify-content-center"
+      :size="50"
+    >
+      11
+    </SplitterPanel>
+    <SplitterPanel
+      v-if="splitInfo['children'][1] != null"
+      class="flex align-items-center justify-content-center"
+      :size="50"
+    >
+      22
+    </SplitterPanel>
+    <BaseComponent
+      v-if="splitInfo.se == 'component'"
+      :component="splitInfo.component"
+      @changeselectLayout="changeselectLayout"
+      @Click.stop="clickLayout(splitInfo)"
+    ></BaseComponent>
+  </Splitter>
+  <Splitter
+    v-else
+    :id="splitInfo['layout_nm']"
+    class="mb-5"
+    :layout="splitInfo['layout_type']"
+    @resizeend="updateSizes"
+    @Click.stop="clickLayout(splitInfo)"
+    :style="this.$createStyleSheet(splitInfo.styleSheet)"
+  >
+    <SplitterPanel
+      v-if="splitInfo['children'][0] != null"
+      :id="splitInfo['children'][0]['layout_nm']"
+      class="flex align-items-center justify-content-center"
+      :class="{
+        active:
+          selectLayout['layout_nm'] == splitInfo['children'][0]['layout_nm'] &&
+          splitInfo['children'][0],
+        'padding-1': splitInfo['children'][0]['component'] != null,
+      }"
+      :size="splitInfo['layout_size1']"
+    >
+      <template v-if="splitInfo.children[0]">
+        <SplitterLayout
+          v-if="splitInfo.children[0].se == 'splitter'"
+          :splitInfo="splitInfo.children[0]"
+          @changeselectLayout="changeselectLayout"
+          :selectLayout="selectLayout"
+        ></SplitterLayout>
+        <BaseComponent
+          v-if="splitInfo.children[0].se == 'component'"
+          :component="splitInfo.children[0].component"
+          @changeselectLayout="changeselectLayout"
+          @Click.stop="clickLayout(splitInfo.children[0])"
+        ></BaseComponent>
+      </template>
+    </SplitterPanel>
+    <SplitterPanel
+      v-if="splitInfo['children'][1] != null"
+      :id="splitInfo['children'][1]['layout_nm']"
+      class="flex align-items-center justify-content-center"
+      :class="{
+        active:
+          selectLayout['layout_nm'] == splitInfo['children'][1]['layout_nm'] &&
+          splitInfo['children'][1],
+        'padding-1': splitInfo['children'][1]['component'] != null,
+      }"
+      :size="splitInfo['layout_size2']"
+    >
+      <template v-if="splitInfo.children[1]">
+        <SplitterLayout
+          v-if="splitInfo.children[1].se == 'splitter'"
+          :splitInfo="splitInfo.children[1]"
+          @changeselectLayout="changeselectLayout"
+          :selectLayout="selectLayout"
+        ></SplitterLayout>
+        <BaseComponent
+          v-if="splitInfo.children[1].se == 'component'"
+          :component="splitInfo.children[1].component"
+          @changeselectLayout="changeselectLayout"
+          @Click.stop="clickLayout(splitInfo.children[1])"
+        ></BaseComponent>
+      </template>
+    </SplitterPanel>
+  </Splitter>
+</template>
+
+
+<script>
+import BaseComponent from "./elementComponent/BaseComponent.vue";
+
+export default {
+  name: "SplitterLayout",
+  props: {
+    splitInfo: {
+      type: Object,
+    },
+    selectLayout: {
+      type: Object,
+      default: function () {
+        return {
+          layout_nm: "",
+          layout_type: "",
+          children: [],
+        };
+      },
+    },
+    createChartData: {
+      type: Object,
+    },
+  },
+  data() {
+    return {
+      currentSelectLayout: this.selectLayout,
+    };
+  },
+
+  components: {
+    BaseComponent: BaseComponent,
+  },
+
+  methods: {
+    updateSizes(newSizes) {
+      // newSizes는 사용자가 조정한 후의 panel 크기 비율을 배열로 제공합니다.
+      this.splitInfo["layout_size1"] = newSizes.sizes[0];
+      this.splitInfo["layout_size2"] = newSizes.sizes[1];
+      this.splitInfo["sizes"] = newSizes.sizes;
+    },
+    parentInfo: function (parentInfo) {
+      this.$emit("parentInfo", parentInfo);
+    },
+
+    clickLayout: function (splitInfo) {
+      this.$emit("changeselectLayout", splitInfo);
+    },
+
+    changeselectLayout: function (layout) {
+      this.$emit("changeselectLayout", layout);
+    },
+  },
+
+  watch: {
+    splitInfo: {
+      handler: function (newVal, oldVal) {},
+      deep: true,
+    },
+    selectLayout: {
+      handler: function (v, old) {
+        this.currentSelectLayout = v;
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+<style scoped>
+.active {
+  border: 3px dotted red;
+}
+.padding-1 {
+  padding: 1rem;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/chart/BubbleChart.vue (added)
+++ client/views/component/chart/BubbleChart.vue
@@ -0,0 +1,264 @@
+<template>
+    <button @click="createChart">Create Chart</button>
+    <div class="chartdiv" id="chartdiv" ref="chartdiv" style="width: 100%; height: 700px;"></div>
+</template>
+
+<script>
+    import * as am5 from "@amcharts/amcharts5";
+    import * as am5xy from "@amcharts/amcharts5/xy";
+    import * as am5percent from "@amcharts/amcharts5/percent";
+    import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+    
+export default {
+    props: {
+        datalist: {
+            type: Array,
+            default: []
+        },
+        chartName: {
+            type: String,
+        }
+    },
+    data() {
+        return{
+            jsonData: [{
+                            "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
+                        }],
+        }
+        
+    },
+    components: {
+
+    },
+    methods: {
+        // 버블차트 생성
+        initializeChartArea:function(){
+            let chartWarp = this.$refs["chartdiv"]; // 차트 상위 div ref 매칭
+            chartWarp.innerHTML = ""; // 차트 상위 div 내용 초기화 (기존 차트 삭제)
+
+            let div = document.createElement("div"); // 차트를 담을 빈 div 생성 (차트 하위 div)
+            div.style.width = "100%"; // 차트를 담을 div의 넓이
+            div.style.height = "100%"; // 차트를 담을 div의 높이
+            chartWarp.appendChild(div); // 차트 상위 div 안에 차트 하위 div를 추가
+
+            let chartArea = am5.Root.new(chartWarp.firstChild);
+
+            chartArea.setThemes([am5themes_Animated.new(root)]);
+            
+            return chartArea
+        },
+        createChart : function() {
+            
+            let vm = this;
+            let data = vm.jsonData;
+            let root = vm.initializeChartArea(); // 차트 초기화
+
+
+            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]
+                }));
+
+                // 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"
+                }));
+
+                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);
+            root._logo.dispose();
+        },
+    },
+    watch:{
+        datalist: {
+            handler: function (newVal, oldVal) {
+                this.jsonData = newVal;
+                this.createChart(this.jsonData);
+            },
+            deep: true
+        },
+
+    },
+    mounted() {
+        // this.jsonData = this.datalist;
+    },
+};
+</script>
+
+<style scoped>
+#chartdiv {
+    width: 100%;
+    height: 500px;
+}
+button{
+    margin: 10px;
+    background-color: antiquewhite;
+    border-radius: 10px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/chart/ChartCmmn.vue (added)
+++ client/views/component/chart/ChartCmmn.vue
@@ -0,0 +1,953 @@
+<template>
+  <!-- <div>
+        <select name="x-axis" v-model="selectedSubGroup" @change="changeSelectSubGroup">
+            <option key="" value="" >sub 선택</option>
+            <option v-for="(column, index) in subGroupArr" :key="index" :value="column.index">{{ column.columnNm }}</option>
+        </select>
+    </div> -->
+  <div
+    class="chartdiv"
+    id="chartdiv"
+    ref="chartdiv"
+    style="width: 100%; height: 100%"
+  ></div>
+</template>
+
+<script>
+import * as am5 from "@amcharts/amcharts5";
+import * as am5xy from "@amcharts/amcharts5/xy";
+import * as am5percent from "@amcharts/amcharts5/percent";
+import * as am5wc from "@amcharts/amcharts5/wc";
+import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+import Frozen from "@amcharts/amcharts5/themes/Frozen";
+import Dark from "@amcharts/amcharts5/themes/Dark"; // 배경이 어두워야 이쁨
+import Dataviz from "@amcharts/amcharts5/themes/Dataviz";
+import Kelly from "@amcharts/amcharts5/themes/Kelly";
+import Material from "@amcharts/amcharts5/themes/Material";
+import Micro from "@amcharts/amcharts5/themes/Micro";
+import Moonrise from "@amcharts/amcharts5/themes/Moonrise";
+import Spirited from "@amcharts/amcharts5/themes/Spirited";
+import chartDataTransform from "./chartDataTransform";
+
+export default {
+  props: {
+    createDataObj: {
+      type: Object,
+      default: {},
+    },
+  },
+  data() {
+    return {
+      // 가공된 데이터를 담을 배열
+      chartJsondData: [],
+      // select box에서 선택 된 그룹
+      selectedSubGroup: "",
+
+      // group 선택에 따른 변수
+      isSelectGroup: false,
+      subGroupArr: [],
+      groupsResult: [],
+
+      //
+      datalist: [],
+      chartName: this.createDataObj.chart_knd,
+      selectedFieldArr: [],
+      isMultiData: this.createDataObj.isMultiData,
+      selectedCal: this.createDataObj.chart_cal,
+      selectedTheme: "",
+      // selectedGroupNm: ,
+    };
+  },
+  components: {},
+  methods: {
+    // 그룹 선택으로 인한 데이터 필터링 함수
+    changeSelectSubGroup: function (event) {
+      this.selectedSubGroup = event.target.value;
+      // 필터 적용 함수 호출
+      const filterDataList = chartDataTransform.setFilteringData(
+        this.datalist,
+        this.subGroupArr,
+        this.selectedSubGroup
+      );
+      this.transformData(filterDataList);
+    },
+    changeSubGroupData: function (val) {
+      if (Array.isArray(val)) {
+        this.subGroupArr = [];
+        this.subGroupArr = val
+          .map((data) => data.group)
+          .filter((value, index, self) => self.indexOf(value) === index)
+          .map((column, index) => {
+            return {
+              index: index,
+              columnNm: column,
+            };
+          });
+        // subGroupArr sort 처리
+        this.subGroupArr.sort((a, b) =>
+          a.columnNm < b.columnNm ? -1 : a.columnNm > b.columnNm ? 1 : 0
+        );
+      }
+    },
+    //y 객체 배열 생성
+    createYData: function () {
+      this.selectedFieldArr = this.createDataObj.valueAxis.map((field) => {
+        return {
+          valNm: field.columnNm,
+          valIndex: field.columnIdx,
+        };
+      });
+    },
+
+    // 데이터 생성 함수
+    // dataList를 받아와 차트에 표현할 데이터로 가공
+    transformData: function (val) {
+      if (val == null) {
+        this.chartJsondData = [];
+        this.runCreateChart();
+        return;
+      }
+      this.createYData();
+      if (this.selectedCal === "default") {
+        this.chartJsondData = val;
+        this.runCreateChart();
+        return;
+      }
+      // 전체 데이터를 그룹화하여 가공 (All Data)
+      this.groupsResult = chartDataTransform.dataGrouping(
+        val,
+        this.selectedFieldArr
+      );
+      if (!this.isSelectGroup) {
+        // groupSubArr에 group의 key를 중복을 제거하고 담음
+        this.changeSubGroupData(this.datalist);
+      }
+      // 데이터 가공 함수
+      this.chartJsondData = chartDataTransform.calculateSetting(
+        this.groupsResult,
+        this.selectedCal
+      );
+
+      this.runCreateChart();
+    },
+
+    /**
+     * 차트 테마 선택 기능
+     */
+    choeseTheme: function (choeseTheme) {
+      if (choeseTheme === "Frozen") {
+        return Frozen;
+      } else if (choeseTheme === "Dark") {
+        return Dark;
+      } else if (choeseTheme === "Dataviz") {
+        return Dataviz;
+      } else if (choeseTheme === "Kelly") {
+        return Kelly;
+      } else if (choeseTheme === "Material") {
+        return Material;
+      } else if (choeseTheme === "Micro") {
+        return Micro;
+      } else if (choeseTheme === "Moonrise") {
+        return Moonrise;
+      } else if (choeseTheme === "Spirited") {
+        return Spirited;
+      }
+    },
+
+    initializeChartArea: function () {
+      let chartWarp = this.$refs["chartdiv"]; // 차트 상위 div ref 매칭
+      if(chartWarp){
+        chartWarp.innerHTML = ""; // 차트 상위 div 내용 초기화 (기존 차트 삭제)
+
+        let div = document.createElement("div"); // 차트를 담을 빈 div 생성 (차트 하위 div)
+        div.style.width = "100%"; // 차트를 담을 div의 넓이
+        div.style.height = "100%"; // 차트를 담을 div의 높이
+        chartWarp.appendChild(div); // 차트 상위 div 안에 차트 하위 div를 추가
+
+        let chartArea = am5.Root.new(chartWarp.firstChild);
+
+        let themes = this.choeseTheme(this.selectedTheme);
+
+        if (this.selectedTheme != "") {
+          chartArea.setThemes([am5themes_Animated.new(root), themes.new(root)]);
+        } else {
+          chartArea.setThemes([am5themes_Animated.new(root)]);
+        }
+      return chartArea;}
+    },
+    /* Create chart */
+    // column chart
+    createColumnChart: function () {
+      // 필요 데이터 및 변수 선언
+      let vm = this;
+      let data = vm.chartJsondData;
+      let root = vm.initializeChartArea(); // 차트 초기화
+
+      let chartConfig = vm.getChartConfig(vm.chartName);
+      let chart = vm.initializeChart(root, chartConfig);
+      let series = null;
+
+      let cursor = chart.set(
+        "cursor",
+        am5xy.XYCursor.new(root, {
+          behavior: chartConfig.behavior,
+        })
+      );
+      // 축 라인 숨김
+      // cursor.lineY.set("forceHidden", true);
+      // cursor.lineX.set("forceHidden", true);
+
+      let categoryRenderer = am5xy[chartConfig.categoryAxisRenderer].new(root, {
+        minGridDistance: 30,
+        minorGridEnabled: true,
+        inversed: chartConfig.categoryInversed,
+      });
+
+      // 카테고리 렌더러 옵션 설정
+      categoryRenderer.labels.template.setAll({
+        rotation: -90,
+        centerY: am5.p50,
+        centerX: am5.p100,
+        paddingRight: 15,
+      });
+      categoryRenderer.grid.template.setAll({
+        stroke: am5.color(0xf15f5f),
+        location: 1,
+      });
+
+      // 카테고리 축 설정
+      let categoryAxis = chart[chartConfig.categoryAxes].push(
+        am5xy.CategoryAxis.new(root, {
+          maxDeviation: 0.3,
+          categoryField: "categoryData",
+          renderer: categoryRenderer,
+          tooltip: am5.Tooltip.new(root, {}),
+        })
+      );
+
+      // value 랜더러 설정
+      let valueRenderer = am5xy[chartConfig.valueAxisRenderer].new(root, {
+        strokeOpacity: 0.1,
+      });
+
+      // value 축 설정
+      let valueAxis = chart[chartConfig.valueAxes].push(
+        am5xy.ValueAxis.new(root, {
+          maxDeviation: 0.3,
+          renderer: valueRenderer,
+        })
+      );
+
+      categoryAxis.data.setAll(data);
+
+      // 시리즈 설정
+      let seriesConfig = vm.getSeriesConfig(
+        vm.chartName,
+        categoryAxis,
+        valueAxis
+      );
+
+      let valuedata =
+        vm.selectedFieldArr.length > 0 ? vm.selectedFieldArr[0].valNm : "";
+
+      series = vm.initializeSeries(chart, seriesConfig, valuedata);
+
+      series.columns.template.setAll({
+        cornerRadiusTL: 5,
+        cornerRadiusTR: 5,
+        strokeOpacity: 0,
+      });
+      series.columns.template.adapters.add("fill", function (fill, target) {
+        return chart.get("colors").getIndex(series.columns.indexOf(target));
+      });
+
+      series.columns.template.adapters.add("stroke", function (stroke, target) {
+        return chart.get("colors").getIndex(series.columns.indexOf(target));
+      });
+
+      series.data.setAll(data);
+      series.appear(1000);
+      root._logo.dispose();
+    },
+
+    // line chart
+    createLineChart: function () {
+      let vm = this;
+      let data = vm.chartJsondData;
+      let root = vm.initializeChartArea(); // 차트 초기화
+
+      let chartConfig = vm.getChartConfig(vm.chartName);
+      let chart = vm.initializeChart(root, chartConfig);
+      let series = null;
+
+      let categoryRenderer = am5xy[chartConfig.categoryAxisRenderer].new(root, {
+        minorGridEnabled: true,
+        minGridDistance: 60,
+      });
+      categoryRenderer.grid.template.setAll({
+        location: 1,
+      });
+
+      let categoryAxis = chart[chartConfig.categoryAxes].push(
+        am5xy.CategoryAxis.new(root, {
+          categoryField: "categoryData",
+          renderer: categoryRenderer,
+          tooltip: am5.Tooltip.new(root, {}),
+        })
+      );
+
+      categoryAxis.data.setAll(data);
+
+      let valueRenderer = am5xy[chartConfig.valueAxisRenderer].new(root, {
+        strokeOpacity: 0.1,
+      });
+
+      let valueAxis = chart[chartConfig.valueAxes].push(
+        am5xy.ValueAxis.new(root, {
+          min: 0,
+          renderer: valueRenderer,
+        })
+      );
+
+      let cursor = chart.set(
+        "cursor",
+        am5xy.XYCursor.new(root, {
+          behavior: "none",
+        })
+      );
+      cursor.lineY.set("visible", false);
+
+      let seriesConfig = vm.getSeriesConfig(
+        vm.chartName,
+        categoryAxis,
+        valueAxis
+      );
+
+      //데이터 없애는 부분 생각 필요
+      let valuedata =
+        vm.selectedFieldArr.length > 0 ? vm.selectedFieldArr[0].valNm : "value";
+      for (let i = 0; i < vm.selectedFieldArr.length; i++) {
+        series = vm.makeSeries(
+          root,
+          chart,
+          vm.selectedFieldArr[i].valNm,
+          seriesConfig
+        );
+
+        // 노드 표시
+        if (vm.chartName == "node_line_chart") {
+          series.bullets.push(function (root) {
+            return am5xy.AxisBullet.new(root, {
+              location: 0.5,
+              sprite: am5.Circle.new(root, {
+                radius: 5,
+                fill: am5.color(0xff0000),
+              }),
+            });
+          });
+        }
+
+        // 그래프 배경 색상
+        if (vm.chartName == "liin_chart_back") {
+          series.fills.template.setAll({
+            fillOpacity: 0.2,
+            visible: true,
+          });
+        }
+
+        series.data.setAll(data);
+      }
+
+      series.appear(1000, 100);
+
+      root._logo.dispose();
+    },
+
+    // pie_chart chart
+    createPieChart: function () {
+      let data = this.chartJsondData;
+
+      let root = this.initializeChartArea(); // 차트 초기화
+
+      let chart = null;
+      let series = null;
+
+      // Create chart
+      let startAngle = null;
+      let endAngle = null;
+      let innerRadius = null;
+
+      if (this.chartName == "semicircle_chart") {
+        startAngle = 180;
+        endAngle = 360;
+      }
+
+      if (this.chartName == "dounet_chart") {
+        innerRadius = am5.percent(50);
+      }
+
+      chart = root.container.children.push(
+        am5percent.PieChart.new(root, {
+          startAngle: startAngle,
+          endAngle: 360,
+          innerRadius: innerRadius,
+        })
+      );
+
+      let valuedata =
+        this.selectedFieldArr.length > 0
+          ? this.selectedFieldArr[0].valNm
+          : "value";
+
+      series = chart.series.push(
+        am5percent.PieSeries.new(root, {
+          valueField: valuedata,
+          categoryField: "categoryData",
+          startAngle: this.chartName == "semicircle_chart" ? 180 : null,
+          endAngle: 360,
+        })
+      );
+
+      series.states.create("hidden", {
+        endAngle: -90,
+      });
+
+      series.data.setAll(data);
+
+      series.appear(1000, 100);
+
+      root._logo.dispose();
+    },
+
+    // Clustered data chart
+    createClusteredChart: function () {
+      let vm = this;
+      let root = this.initializeChartArea(); // 차트 초기화'
+      let dataVal = this.chartJsondData;
+      let chartConfig = this.getChartConfig(vm.chartName);
+      let chart = this.initializeChart(root, chartConfig);
+
+      // Add legend
+      let legend = chart.children.push(
+        am5.Legend.new(root, {
+          centerX: am5.p50,
+          x: am5.p50,
+        })
+      );
+
+      let categoryRenderer = am5xy[chartConfig.categoryAxisRenderer].new(root, {
+        cellStartLocation: 0.1,
+        cellEndLocation: 0.9,
+        minorGridEnabled: true,
+      });
+
+      let categoryAxis = chart.xAxes.push(
+        am5xy.CategoryAxis.new(root, {
+          categoryField: "categoryData",
+          renderer: categoryRenderer,
+          tooltip: am5.Tooltip.new(root, {}),
+        })
+      );
+
+      categoryRenderer.grid.template.setAll({
+        location: 1,
+      });
+
+      categoryAxis.data.setAll(dataVal);
+
+      // value 랜더러 설정
+      let valueRenderer = am5xy[chartConfig.valueAxisRenderer].new(root, {
+        strokeOpacity: 0.1,
+      });
+
+      let valueAxis = chart[chartConfig.valueAxes].push(
+        am5xy.ValueAxis.new(root, {
+          renderer: valueRenderer,
+        })
+      );
+
+      let seriesConfig = vm.getSeriesConfig(
+        vm.chartName,
+        categoryAxis,
+        valueAxis
+      );
+      let series = null;
+      for (let i = 0; i < vm.selectedFieldArr.length; i++) {
+        series = vm.makeSeries(
+          root,
+          chart,
+          vm.selectedFieldArr[i].valNm,
+          seriesConfig
+        );
+
+        series.data.setAll(dataVal);
+      }
+
+      chart.appear(1000, 300);
+
+      root._logo.dispose(); //amChart 로고 삭제
+    },
+    createWordChart: function () {
+      let vm = this;
+      let root = vm.initializeChartArea(); // 차트 초기화
+
+      let valuedata = vm.selectedFieldArr.length > 0 ? vm.selectedFieldArr[0].valNm : "";
+
+      let series = root.container.children.push(
+        am5wc.WordCloud.new(root, {
+          categoryField: "categoryData",
+          valueField: valuedata,
+          maxFontSize: am5.percent(15),
+        })
+      );
+
+      // Configure labels
+      series.labels.template.setAll({
+        fontFamily: "Courier New",
+      });
+
+      series.data.setAll(this.chartJsondData);
+      root._logo.dispose();
+    },
+    // 음수 차트 수정 필요(아직 합치지않음)
+    createNagativeChart: function () {
+      let vm = this;
+      let root = vm.initializeChartArea(); // 차트 초기화
+
+      root._logo.dispose();
+
+      if (vm.selectedFieldArr.length < 2) {
+        return;
+      }
+
+      chartDataTransform.convertValuesToNegative(
+        vm.chartJsondData,
+        vm.selectedFieldArr[0].valNm
+      );
+
+      let chart = root.container.children.push(
+        am5xy.XYChart.new(root, {
+          panX: false,
+          panY: false,
+          wheelX: "panX",
+          wheelY: "zoomX",
+          layout: root.verticalLayout,
+          arrangeTooltips: false,
+          paddingLeft: 0,
+          paddingRight: 10,
+        })
+      );
+
+      // Use only absolute numbers
+      chart.getNumberFormatter().set("numberFormat", "#.#s");
+
+      let legend = chart.children.push(
+        am5.Legend.new(root, {
+          centerX: am5.p50,
+          x: am5.p50,
+        })
+      );
+
+      let yAxis = chart.yAxes.push(
+        am5xy.CategoryAxis.new(root, {
+          categoryField: "categoryData",
+          renderer: am5xy.AxisRendererY.new(root, {
+            inversed: true,
+            cellStartLocation: 0.1,
+            cellEndLocation: 0.9,
+            minorGridEnabled: true,
+            minGridDistance: 20,
+          }),
+        })
+      );
+
+      yAxis.data.setAll(this.chartJsondData);
+
+      let xAxis = chart.xAxes.push(
+        am5xy.ValueAxis.new(root, {
+          renderer: am5xy.AxisRendererX.new(root, {
+            minGridDistance: 60,
+            strokeOpacity: 0.1,
+          }),
+        })
+      );
+
+      function createSeries(
+        field,
+        labelCenterX,
+        pointerOrientation,
+        rangeValue
+      ) {
+        let series = chart.series.push(
+          am5xy.ColumnSeries.new(root, {
+            xAxis: xAxis,
+            yAxis: yAxis,
+            valueXField: field,
+            categoryYField: "categoryData",
+            sequencedInterpolation: true,
+            clustered: false,
+            tooltip: am5.Tooltip.new(root, {
+              pointerOrientation: pointerOrientation,
+              labelText: "{categoryY}: {valueX}",
+            }),
+          })
+        );
+
+        series.columns.template.setAll({
+          height: am5.p100,
+          strokeOpacity: 0,
+          fillOpacity: 0.8,
+        });
+
+        series.bullets.push(function () {
+          return am5.Bullet.new(root, {
+            locationX: 1,
+            locationY: 0.5,
+            sprite: am5.Label.new(root, {
+              centerY: am5.p50,
+              text: "{valueX}",
+              populateText: true,
+              centerX: labelCenterX,
+            }),
+          });
+        });
+
+        series.data.setAll(vm.chartJsondData);
+        series.appear();
+
+        let rangeDataItem = xAxis.makeDataItem({
+          value: rangeValue,
+        });
+        xAxis.createAxisRange(rangeDataItem);
+        rangeDataItem.get("grid").setAll({
+          strokeOpacity: 1,
+          stroke: series.get("stroke"),
+        });
+
+        let label = rangeDataItem.get("label");
+        label.setAll({
+          text: field.toUpperCase(),
+          fontSize: "1.1em",
+          fill: series.get("stroke"),
+          paddingTop: 10,
+          isMeasured: false,
+          centerX: labelCenterX,
+        });
+        label.adapters.add("dy", function () {
+          return -chart.plotContainer.height();
+        });
+        return series;
+      }
+
+      createSeries(vm.selectedFieldArr[0].valNm, am5.p100, "right", -3);
+      createSeries(vm.selectedFieldArr[1].valNm, 0, "left", 4);
+
+      let cursor = chart.set(
+        "cursor",
+        am5xy.XYCursor.new(root, {
+          behavior: "zoomY",
+        })
+      );
+      cursor.lineY.set("forceHidden", true);
+      cursor.lineX.set("forceHidden", true);
+
+      chart.appear(1000, 100);
+      root._logo.dispose(); //amChart 로고 삭제
+    },
+    // 막대 선 차트 (수정 필요)
+    createColAndLineChart: function () {
+      let vm = this;
+      let data = vm.chartJsondData;
+      let root = vm.initializeChartArea(); // 차트 초기화
+
+      root._logo.dispose(); //amChart 로고 삭제
+
+      if (data.length == 0) {
+        return;
+      }
+
+      let income = Object.keys(data[0])[1];
+      let expenses = Object.keys(data[0])[2];
+
+      let chart = root.container.children.push(
+        am5xy.XYChart.new(root, {
+          panX: false,
+          panY: false,
+          wheelX: "panX",
+          wheelY: "zoomX",
+          paddingLeft: 0,
+          layout: root.verticalLayout,
+        })
+      );
+
+      let xRenderer = am5xy.AxisRendererX.new(root, {
+        minorGridEnabled: true,
+        minGridDistance: 60,
+      });
+      let xAxis = chart.xAxes.push(
+        am5xy.CategoryAxis.new(root, {
+          categoryField: "categoryData",
+          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,
+          }),
+        })
+      );
+
+      let series1 = chart.series.push(
+        am5xy.ColumnSeries.new(root, {
+          name: income,
+          xAxis: xAxis,
+          yAxis: yAxis,
+          valueYField: income,
+          categoryXField: "categoryData",
+          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: "categoryData",
+          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, {}));
+
+      series1.appear(1000);
+      chart.appear(1000, 300);
+      root._logo.dispose(); //amChart 로고 삭제
+    },
+    // Create chart
+    getChartConfig: function (chartName) {
+      // 차트에 따라 설정이 달라져야함
+      let axis = true;
+      if (
+        chartName == "column_chart_h" ||
+        chartName == "clustered_chart_h" ||
+        chartName == "STACK_ROW"
+      ) {
+        axis = false;
+      }
+
+      return {
+        // 차트 축 설정
+        categoryAxisRenderer: axis ? "AxisRendererX" : "AxisRendererY",
+        valueAxisRenderer: axis ? "AxisRendererY" : "AxisRendererX",
+        categoryAxes: axis ? "xAxes" : "yAxes",
+        valueAxes: axis ? "yAxes" : "xAxes",
+        categoryInversed: axis ? false : true,
+        // 차트 이동 및 확대/축소 설정
+        panX: true,
+        panY: true,
+        wheelX: "panX",
+        wheelY: axis ? "panY" : "zoomX",
+        behavior: axis ? "none" : "zoomY",
+      };
+    },
+    initializeChart: function (root, config) {
+      console.log(root.container.children);
+      return root.container.children.push(
+        am5xy.XYChart.new(root, {
+          panX: config.panX,
+          panY: config.panY,
+          wheelX: config.wheelX,
+          wheelY: config.wheelY,
+        })
+      );
+    },
+
+    // Create series
+    getSeriesConfig: function (chartName, categoryAxis, valueAxis) {
+      // 차트에 따라 설정이 달라져야함
+      let axis = true;
+      let chartNm = "ColumnSeries";
+      if (
+        chartName == "column_chart_h" ||
+        chartName == "clustered_chart_h" ||
+        chartName == "STACK_ROW"
+      ) {
+        axis = false;
+      }
+      if (
+        chartName == "line_chart" ||
+        chartName == "node_line_chart" ||
+        chartName == "liin_chart_back"
+      ) {
+        chartNm = "LineSeries";
+      }
+      let isStack = false;
+
+      if (
+        this.chartName === "stacked_column_chart" ||
+        this.chartName === "STACK_ROW"
+      ) {
+        isStack = true;
+      }
+
+      return {
+        chartNm: chartNm,
+        isStack: isStack,
+        xAxis: axis ? categoryAxis : valueAxis,
+        yAxis: axis ? valueAxis : categoryAxis,
+        categoryField: axis ? "categoryXField" : "categoryYField",
+        valueField: axis ? "valueYField" : "valueXField",
+        categoryXY: axis ? "categoryX" : "categoryY",
+        valueXY: axis ? "valueY" : "valueX",
+        tooltipText: axis ? "{valueY}" : "{valueX}",
+      };
+    },
+    initializeSeries: function (chart, seriesConfig, valuedata) {
+      // 차트에 새로운 시리즈를 생성하여 추가
+      let series = chart.series.push(
+        am5xy[seriesConfig.chartNm].new(chart.root, {
+          name: valuedata, // 이 부분은 valuedata를 이름으로 사용하고 있으나, 이는 데이터의 의도에 따라 다를 수 있습니다.
+          stacked: seriesConfig.isStack,
+          xAxis: seriesConfig.xAxis,
+          yAxis: seriesConfig.yAxis,
+          [seriesConfig.valueField]: valuedata,
+          [seriesConfig.categoryField]: "categoryData",
+          tooltip: am5.Tooltip.new(chart.root, {
+            labelText: seriesConfig.tooltipText,
+          }),
+        })
+      );
+
+      return series; // 생성된 시리즈 객체를 반환
+    },
+    // Add series
+    // https://www.amcharts.com/docs/v5/charts/xy-chart/series/
+    makeSeries: function (root, chart, name, seriesConfig) {
+      let series = chart.series.push(
+        am5xy[seriesConfig.chartNm].new(root, {
+          name: name,
+          stacked: seriesConfig.isStack,
+          xAxis: seriesConfig.xAxis,
+          yAxis: seriesConfig.yAxis,
+          [seriesConfig.valueField]: name,
+          [seriesConfig.categoryField]: "categoryData",
+          tooltip: am5.Tooltip.new(chart.root, {
+            labelText: seriesConfig.tooltipText,
+          }),
+        })
+      );
+
+      return series;
+    },
+
+    // 차트 생성 실행 함수
+    runCreateChart: function () {
+      if (
+        this.chartName === "column_chart_v" ||
+        this.chartName === "column_chart_h"
+      ) {
+        this.createColumnChart();
+      } else if (
+        this.chartName === "line_chart" ||
+        this.chartName === "node_line_chart" ||
+        this.chartName === "liin_chart_back"
+      ) {
+        this.createLineChart();
+      } else if (
+        this.chartName === "pie_chart" ||
+        this.chartName === "dounet_chart" ||
+        this.chartName === "semicircle_chart"
+      ) {
+        this.createPieChart();
+      } else if (
+        this.chartName === "clustered_chart_v" ||
+        this.chartName === "clustered_chart_h" ||
+        this.chartName === "stacked_column_chart" ||
+        this.chartName === "STACK_ROW"
+      ) {
+        this.createClusteredChart();
+      } else if (this.chartName === "word_chart") {
+        this.createWordChart();
+      } else if (this.chartName === "stacked_bar_chart") {
+        this.createNagativeChart();
+      } else if (this.chartName === "mix_chart") {
+        this.createColAndLineChart();
+      }
+    },
+  },
+  watch: {
+    createDataObj: {
+      handler: function (newVal, oldVal) {
+        this.datalist = newVal.data_list;
+        this.chartName = newVal.chart_knd;
+        this.isMultiData = newVal.multidata_use_yn;
+        this.selectedCal = newVal.chart_cal;
+        this.transformData(this.datalist);
+      },
+      deep: true,
+    },
+  },
+  mounted() {
+    this.datalist = this.createDataObj.data_list;
+    this.chartName = this.createDataObj.chart_knd;
+    this.isMultiData = this.createDataObj.multidata_use_yn;
+    this.selectedCal = this.createDataObj.chart_cal;
+    this.transformData(this.datalist);
+  },
+};
+</script>
+
+<style scoped>
+#chartdiv {
+  width: 100%;
+  height: 500px;
+}
+button {
+  margin: 10px;
+  background-color: antiquewhite;
+  border-radius: 10px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/chart/ChartPage.vue (added)
+++ client/views/component/chart/ChartPage.vue
@@ -0,0 +1,1665 @@
+<template>
+  <div style="width: 500px; height: 300px">
+    <JobContainer :jobGroup="jobGroup" @getDataTable="getResult" />
+  </div>
+  <div>
+    <button name="ChartCmmn" @click="changeChartName($event, 'column_chart_v')">
+      가로 막대차트
+    </button>
+    <button name="ChartCmmn" @click="changeChartName($event, 'column_chart_h')">
+      세로 막대차트
+    </button>
+    <button
+      name="ChartCmmn"
+      @click="changeChartName($event, 'clustered_chart_v')"
+    >
+      가로 클러스터
+    </button>
+    <button
+      name="ChartCmmn"
+      @click="changeChartName($event, 'clustered_chart_h')"
+    >
+      세로 클러스터
+    </button>
+    <button
+      name="WordChart"
+      @click="changeChartName($event, 'stacked_bar_chart')"
+    >
+      음수 차트
+    </button>
+    <button name="ChartCmmn" @click="changeChartName($event, 'line_chart')">
+      선 차트
+    </button>
+    <button name="ColAndLine" @click="changeChartName($event, 'mix_chart')">
+      선 막대 차트
+    </button>
+    <button name="ChartCmmn" @click="changeChartName($event, 'pie_chart')">
+      파이 차트
+    </button>
+    <button name="ChartCmmn" @click="changeChartName($event, 'dounet_chart')">
+      도넛 차트
+    </button>
+    <button
+      name="ChartCmmn"
+      @click="changeChartName($event, 'semicircle_chart')"
+    >
+      반원 차트
+    </button>
+    <button name="BubbleChart" @click="changeChartName($event, 'BUBBLE')">
+      버블 차트
+    </button>
+    <button name="ChartCmmn" @click="changeChartName($event, 'word_chart')">
+      워드
+    </button>
+    <button
+      name="ChartCmmn"
+      @click="changeChartName($event, 'stacked_column_chart')"
+    >
+      스택막대차트 - COL
+    </button>
+    <button name="ChartCmmn" @click="changeChartName($event, 'STACK_ROW')">
+      스택막대차트 - ROW
+    </button>
+    <button
+      name="ChartCmmn"
+      @click="changeChartName($event, 'node_line_chart')"
+    >
+      노드 선 차트
+    </button>
+    <button
+      name="ChartCmmn"
+      @click="changeChartName($event, 'liin_chart_back')"
+    >
+      배경 선 차트
+    </button>
+  </div>
+  <div>
+    <select v-model="selectedTheme" @change="setThemes">
+      <option value="">테마 선택</option>
+      <option v-for="theme in themes" :key="theme" :value="theme">
+        {{ theme }}
+      </option>
+    </select>
+  </div>
+  <div>
+    <select name="x-axis" v-model="selectedX" @change="selectedXAxis">
+      <option key="" value="">x축 선택</option>
+      <option
+        v-for="(column, index) in xAxisList"
+        :key="index"
+        :value="column.index"
+      >
+        {{ column.columnNm }}
+      </option>
+    </select>
+    <select name="y-axis" v-model="selectedY" @change="selectedYAxis">
+      <option key="" value="">y축 선택</option>
+      <option
+        v-for="(column, index) in yAxisList"
+        :key="index"
+        :value="column.index"
+      >
+        {{ column.columnNm }}
+      </option>
+    </select>
+    <select name="x-axis" v-model="selectedGroup" @change="changeSelectGroup">
+      <option key="" value="">그룹 선택</option>
+      <option
+        v-for="(column, index) in groupArr"
+        :key="index"
+        :value="column.index"
+      >
+        {{ column.columnNm }}
+      </option>
+    </select>
+  </div>
+  <div style="display: flex">
+    <input type="radio" id="default" value="default" v-model="selectedCal" />
+    default<br />
+    <input type="radio" id="sum" value="sum" v-model="selectedCal" /> Sum<br />
+    <input type="radio" id="avg" value="avg" v-model="selectedCal" />
+    Average<br />
+    <input type="radio" id="min" value="min" v-model="selectedCal" /> Min<br />
+    <input type="radio" id="max" value="max" v-model="selectedCal" /> Max<br />
+  </div>
+  <div>
+    <ul>
+      <li
+        v-for="(field, index) in selectedFieldArr"
+        :key="index"
+        :value="field.valIndex"
+      >
+        {{ field.valNm }}<button @click="deleteField(index)">x</button>
+      </li>
+    </ul>
+  </div>
+  <div class="chartdiv" id="chartdiv" style="width: 100%; height: 700px">
+    <component
+      :is="componentName"
+      :datalist="datalist"
+      :chartName="chartName"
+      :selectedFieldArr="selectedFieldArr"
+      :isMultiData="isMultiData"
+      :selectedCal="selectedCal"
+      :selectedGroupNm="selectedGroupNm"
+      :selectedTheme="selectedTheme"
+    />
+  </div>
+</template>
+
+<script>
+import JobContainer from "../../component/connection/jobContainer.vue";
+
+// 차트 컴포넌트 import
+import ChartCmmn from "./ChartCmmn.vue";
+// import ColumnLablesChart from './ColumnLablesChart.vue';
+// import PieChart from './PieChart.vue';
+// import LineChart from './LineChart.vue';
+// import ClusteredChart from './ClusteredChart.vue';
+// import ColAndLine from './ColAndLine.vue';
+import chartDataTransform from "./chartDataTransform";
+// import BubbleChart from './BubbleChart.vue';
+
+export default {
+  data() {
+    return {
+      // 데이터를 담을 배열
+      datalist: [],
+      dummyDataList: [
+        [1, 2022, "금호읍", "16 - 16", 1, 1, 0],
+        [2, 2022, "금호읍", "18 - 18", 2, 2, 0],
+        [3, 2022, "금호읍", "19 - 19", 2, 2, 0],
+        [4, 2022, "금호읍", "20 - 20", 6, 6, 0],
+        [5, 2022, "금호읍", "21 - 21", 5, 0, 5],
+        [6, 2022, "금호읍", "22 - 22", 7, 3, 4],
+        [7, 2022, "금호읍", "23 - 23", 6, 5, 1],
+        [8, 2022, "금호읍", "24 - 24", 11, 10, 1],
+        [9, 2022, "금호읍", "25 - 25", 16, 11, 5],
+        [10, 2022, "금호읍", "26 - 26", 9, 6, 3],
+        [11, 2022, "금호읍", "27 - 27", 23, 16, 7],
+        [12, 2022, "금호읍", "28 - 28", 12, 8, 4],
+        [13, 2022, "금호읍", "29 - 29", 16, 10, 6],
+        [14, 2022, "금호읍", "30 - 30", 15, 11, 4],
+        [15, 2022, "금호읍", "31 - 31", 21, 16, 5],
+        [16, 2022, "금호읍", "32 - 32", 17, 13, 4],
+        [17, 2022, "금호읍", "33 - 33", 17, 12, 5],
+        [18, 2022, "금호읍", "34 - 34", 21, 10, 11],
+        [19, 2022, "금호읍", "35 - 35", 22, 15, 7],
+        [20, 2022, "금호읍", "36 - 36", 14, 8, 6],
+        [21, 2022, "금호읍", "37 - 37", 16, 9, 7],
+        [22, 2022, "금호읍", "38 - 38", 20, 12, 8],
+        [23, 2022, "금호읍", "39 - 39", 21, 16, 5],
+        [24, 2022, "금호읍", "40 - 40", 15, 12, 3],
+        [25, 2022, "금호읍", "41 - 41", 21, 16, 5],
+        [26, 2022, "금호읍", "42 - 42", 33, 23, 10],
+        [27, 2022, "금호읍", "43 - 43", 33, 28, 5],
+        [28, 2022, "금호읍", "44 - 44", 29, 17, 12],
+        [29, 2022, "금호읍", "45 - 45", 36, 30, 6],
+        [30, 2022, "금호읍", "46 - 46", 44, 28, 16],
+        [31, 2022, "금호읍", "47 - 47", 45, 31, 14],
+        [32, 2022, "금호읍", "48 - 48", 41, 30, 11],
+        [33, 2022, "금호읍", "49 - 49", 43, 34, 9],
+        [34, 2022, "금호읍", "50 - 50", 61, 42, 19],
+        [35, 2022, "금호읍", "51 - 51", 71, 46, 25],
+        [36, 2022, "금호읍", "52 - 52", 63, 39, 24],
+        [37, 2022, "금호읍", "53 - 53", 70, 48, 22],
+        [38, 2022, "금호읍", "54 - 54", 86, 60, 26],
+        [39, 2022, "금호읍", "55 - 55", 80, 49, 31],
+        [40, 2022, "금호읍", "56 - 56", 87, 60, 27],
+        [41, 2022, "금호읍", "57 - 57", 102, 74, 28],
+        [42, 2022, "금호읍", "58 - 58", 110, 64, 46],
+        [43, 2022, "금호읍", "59 - 59", 89, 62, 27],
+        [44, 2022, "금호읍", "60 - 60", 114, 80, 34],
+        [45, 2022, "금호읍", "61 - 61", 124, 85, 39],
+        [46, 2022, "금호읍", "62 - 62", 106, 65, 41],
+        [47, 2022, "금호읍", "63 - 63", 109, 67, 42],
+        [48, 2022, "금호읍", "64 - 64", 88, 51, 37],
+        [49, 2022, "금호읍", "65 - 65", 96, 50, 46],
+        [50, 2022, "금호읍", "66 - 66", 83, 55, 28],
+        [51, 2022, "금호읍", "67 - 67", 99, 54, 45],
+        [52, 2022, "금호읍", "68 - 68", 72, 41, 31],
+        [53, 2022, "금호읍", "69 - 69", 81, 46, 35],
+        [54, 2022, "금호읍", "70 - 70", 90, 44, 46],
+        [55, 2022, "금호읍", "71 - 71", 57, 31, 26],
+        [56, 2022, "금호읍", "72 - 72", 55, 32, 23],
+        [57, 2022, "금호읍", "73 - 73", 61, 27, 34],
+        [58, 2022, "금호읍", "74 - 74", 63, 24, 39],
+        [59, 2022, "금호읍", "75 - 75", 42, 18, 24],
+        [60, 2022, "금호읍", "76 - 76", 35, 15, 20],
+        [61, 2022, "금호읍", "77 - 77", 56, 15, 41],
+        [62, 2022, "금호읍", "78 - 78", 63, 22, 41],
+        [63, 2022, "금호읍", "79 - 79", 58, 16, 42],
+        [64, 2022, "금호읍", "80 - 80", 63, 19, 44],
+        [65, 2022, "금호읍", "81 - 81", 62, 15, 47],
+        [66, 2022, "금호읍", "82 - 82", 44, 7, 37],
+        [67, 2022, "금호읍", "83 - 83", 56, 10, 46],
+        [68, 2022, "금호읍", "84 - 84", 38, 8, 30],
+        [69, 2022, "금호읍", "85 - 85", 42, 10, 32],
+        [70, 2022, "금호읍", "86 - 86", 37, 4, 33],
+        [71, 2022, "금호읍", "87 - 87", 35, 10, 25],
+        [72, 2022, "금호읍", "88 - 88", 29, 3, 26],
+        [73, 2022, "금호읍", "89 - 89", 15, 1, 14],
+        [74, 2022, "금호읍", "90 - 90", 14, 3, 11],
+        [75, 2022, "금호읍", "91 - 91", 13, 2, 11],
+        [76, 2022, "금호읍", "92 - 92", 10, 1, 9],
+        [77, 2022, "금호읍", "93 - 93", 6, 0, 6],
+        [78, 2022, "금호읍", "94 - 94", 6, 0, 6],
+        [79, 2022, "금호읍", "95 - 95", 1, 1, 0],
+        [80, 2022, "금호읍", "102 - 102", 1, 0, 1],
+        [81, 2022, "청통면", "20 - 20", 3, 1, 2],
+        [82, 2022, "청통면", "22 - 22", 2, 2, 0],
+        [83, 2022, "청통면", "23 - 23", 6, 4, 2],
+        [84, 2022, "청통면", "24 - 24", 9, 5, 4],
+        [85, 2022, "청통면", "25 - 25", 7, 4, 3],
+        [86, 2022, "청통면", "26 - 26", 10, 8, 2],
+        [87, 2022, "청통면", "27 - 27", 11, 5, 6],
+        [88, 2022, "청통면", "28 - 28", 4, 3, 1],
+        [89, 2022, "청통면", "29 - 29", 12, 9, 3],
+        [90, 2022, "청통면", "30 - 30", 7, 7, 0],
+        [91, 2022, "청통면", "31 - 31", 4, 3, 1],
+        [92, 2022, "청통면", "32 - 32", 3, 2, 1],
+        [93, 2022, "청통면", "33 - 33", 3, 3, 0],
+        [94, 2022, "청통면", "34 - 34", 6, 4, 2],
+        [95, 2022, "청통면", "35 - 35", 6, 3, 3],
+        [96, 2022, "청통면", "36 - 36", 5, 4, 1],
+        [97, 2022, "청통면", "37 - 37", 10, 8, 2],
+        [98, 2022, "청통면", "38 - 38", 13, 9, 4],
+        [99, 2022, "청통면", "39 - 39", 6, 3, 3],
+        [100, 2022, "청통면", "40 - 40", 11, 8, 3],
+        [101, 2022, "청통면", "41 - 41", 5, 2, 3],
+        [102, 2022, "청통면", "42 - 42", 5, 4, 1],
+        [103, 2022, "청통면", "43 - 43", 7, 6, 1],
+        [104, 2022, "청통면", "44 - 44", 11, 11, 0],
+        [105, 2022, "청통면", "45 - 45", 9, 9, 0],
+        [106, 2022, "청통면", "46 - 46", 12, 10, 2],
+        [107, 2022, "청통면", "47 - 47", 14, 10, 4],
+        [108, 2022, "청통면", "48 - 48", 17, 12, 5],
+        [109, 2022, "청통면", "49 - 49", 13, 11, 2],
+        [110, 2022, "청통면", "50 - 50", 21, 16, 5],
+        [111, 2022, "청통면", "51 - 51", 19, 14, 5],
+        [112, 2022, "청통면", "52 - 52", 19, 10, 9],
+        [113, 2022, "청통면", "53 - 53", 18, 14, 4],
+        [114, 2022, "청통면", "54 - 54", 27, 22, 5],
+        [115, 2022, "청통면", "55 - 55", 38, 23, 15],
+        [116, 2022, "청통면", "56 - 56", 34, 26, 8],
+        [117, 2022, "청통면", "57 - 57", 28, 21, 7],
+        [118, 2022, "청통면", "58 - 58", 34, 22, 12],
+        [119, 2022, "청통면", "59 - 59", 32, 19, 13],
+        [120, 2022, "청통면", "60 - 60", 25, 19, 6],
+        [121, 2022, "청통면", "61 - 61", 39, 22, 17],
+        [122, 2022, "청통면", "62 - 62", 42, 25, 17],
+        [123, 2022, "청통면", "63 - 63", 37, 22, 15],
+        [124, 2022, "청통면", "64 - 64", 34, 28, 6],
+        [125, 2022, "청통면", "65 - 65", 33, 25, 8],
+        [126, 2022, "청통면", "66 - 66", 33, 21, 12],
+        [127, 2022, "청통면", "67 - 67", 32, 22, 10],
+        [128, 2022, "청통면", "68 - 68", 33, 19, 14],
+        [129, 2022, "청통면", "69 - 69", 20, 10, 10],
+        [130, 2022, "청통면", "70 - 70", 29, 15, 14],
+        [131, 2022, "청통면", "71 - 71", 21, 7, 14],
+        [132, 2022, "청통면", "72 - 72", 20, 7, 13],
+        [133, 2022, "청통면", "73 - 73", 29, 17, 12],
+        [134, 2022, "청통면", "74 - 74", 17, 9, 8],
+        [135, 2022, "청통면", "75 - 75", 27, 13, 14],
+        [136, 2022, "청통면", "76 - 76", 15, 6, 9],
+        [137, 2022, "청통면", "77 - 77", 9, 1, 8],
+        [138, 2022, "청통면", "78 - 78", 23, 4, 19],
+        [139, 2022, "청통면", "79 - 79", 21, 3, 18],
+        [140, 2022, "청통면", "80 - 80", 26, 8, 18],
+        [141, 2022, "청통면", "81 - 81", 20, 4, 16],
+        [142, 2022, "청통면", "82 - 82", 30, 6, 24],
+        [143, 2022, "청통면", "83 - 83", 20, 1, 19],
+        [144, 2022, "청통면", "84 - 84", 16, 1, 15],
+        [145, 2022, "청통면", "85 - 85", 24, 6, 18],
+        [146, 2022, "청통면", "86 - 86", 17, 3, 14],
+        [147, 2022, "청통면", "87 - 87", 13, 1, 12],
+        [148, 2022, "청통면", "88 - 88", 20, 1, 19],
+        [149, 2022, "청통면", "89 - 89", 10, 2, 8],
+        [150, 2022, "청통면", "90 - 90", 5, 0, 5],
+        [151, 2022, "청통면", "91 - 91", 8, 2, 6],
+        [152, 2022, "청통면", "92 - 92", 2, 0, 2],
+        [153, 2022, "청통면", "93 - 93", 5, 2, 3],
+        [154, 2022, "청통면", "94 - 94", 3, 0, 3],
+        [155, 2022, "청통면", "95 - 95", 4, 0, 4],
+        [156, 2022, "청통면", "96 - 96", 1, 0, 1],
+        [157, 2022, "청통면", "97 - 97", 3, 1, 2],
+        [158, 2022, "청통면", "101 - 101", 1, 0, 1],
+        [159, 2022, "신녕면", "15 - 15", 2, 1, 1],
+        [160, 2022, "신녕면", "16 - 16", 31, 14, 17],
+        [161, 2022, "신녕면", "17 - 17", 25, 12, 13],
+        [162, 2022, "신녕면", "18 - 18", 24, 13, 11],
+        [163, 2022, "신녕면", "19 - 19", 3, 2, 1],
+        [164, 2022, "신녕면", "22 - 22", 4, 3, 1],
+        [165, 2022, "신녕면", "23 - 23", 1, 1, 0],
+        [166, 2022, "신녕면", "24 - 24", 3, 3, 0],
+        [167, 2022, "신녕면", "25 - 25", 4, 2, 2],
+        [168, 2022, "신녕면", "26 - 26", 3, 2, 1],
+        [169, 2022, "신녕면", "27 - 27", 2, 2, 0],
+        [170, 2022, "신녕면", "28 - 28", 5, 3, 2],
+        [171, 2022, "신녕면", "29 - 29", 4, 4, 0],
+        [172, 2022, "신녕면", "30 - 30", 8, 4, 4],
+        [173, 2022, "신녕면", "31 - 31", 6, 5, 1],
+        [174, 2022, "신녕면", "32 - 32", 6, 5, 1],
+        [175, 2022, "신녕면", "33 - 33", 6, 4, 2],
+        [176, 2022, "신녕면", "34 - 34", 6, 4, 2],
+        [177, 2022, "신녕면", "35 - 35", 4, 3, 1],
+        [178, 2022, "신녕면", "36 - 36", 5, 3, 2],
+        [179, 2022, "신녕면", "37 - 37", 7, 4, 3],
+        [180, 2022, "신녕면", "38 - 38", 3, 3, 0],
+        [181, 2022, "신녕면", "39 - 39", 7, 7, 0],
+        [182, 2022, "신녕면", "40 - 40", 7, 5, 2],
+        [183, 2022, "신녕면", "41 - 41", 9, 8, 1],
+        [184, 2022, "신녕면", "42 - 42", 8, 5, 3],
+        [185, 2022, "신녕면", "43 - 43", 8, 5, 3],
+        [186, 2022, "신녕면", "44 - 44", 7, 4, 3],
+        [187, 2022, "신녕면", "45 - 45", 9, 6, 3],
+        [188, 2022, "신녕면", "46 - 46", 8, 6, 2],
+        [189, 2022, "신녕면", "47 - 47", 10, 8, 2],
+        [190, 2022, "신녕면", "48 - 48", 7, 6, 1],
+        [191, 2022, "신녕면", "49 - 49", 12, 8, 4],
+        [192, 2022, "신녕면", "50 - 50", 12, 7, 5],
+        [193, 2022, "신녕면", "51 - 51", 16, 10, 6],
+        [194, 2022, "신녕면", "52 - 52", 14, 10, 4],
+        [195, 2022, "신녕면", "53 - 53", 17, 11, 6],
+        [196, 2022, "신녕면", "54 - 54", 30, 11, 19],
+        [197, 2022, "신녕면", "55 - 55", 21, 16, 5],
+        [198, 2022, "신녕면", "56 - 56", 30, 21, 9],
+        [199, 2022, "신녕면", "57 - 57", 29, 16, 13],
+        [200, 2022, "신녕면", "58 - 58", 35, 28, 7],
+        [201, 2022, "신녕면", "59 - 59", 21, 12, 9],
+        [202, 2022, "신녕면", "60 - 60", 37, 26, 11],
+        [203, 2022, "신녕면", "61 - 61", 43, 26, 17],
+        [204, 2022, "신녕면", "62 - 62", 39, 21, 18],
+        [205, 2022, "신녕면", "63 - 63", 41, 20, 21],
+        [206, 2022, "신녕면", "64 - 64", 37, 23, 14],
+        [207, 2022, "신녕면", "65 - 65", 34, 20, 14],
+        [208, 2022, "신녕면", "66 - 66", 31, 15, 16],
+        [209, 2022, "신녕면", "67 - 67", 40, 21, 19],
+        [210, 2022, "신녕면", "68 - 68", 28, 12, 16],
+        [211, 2022, "신녕면", "69 - 69", 26, 11, 15],
+        [212, 2022, "신녕면", "70 - 70", 35, 16, 19],
+        [213, 2022, "신녕면", "71 - 71", 16, 9, 7],
+        [214, 2022, "신녕면", "72 - 72", 21, 11, 10],
+        [215, 2022, "신녕면", "73 - 73", 25, 7, 18],
+        [216, 2022, "신녕면", "74 - 74", 16, 6, 10],
+        [217, 2022, "신녕면", "75 - 75", 26, 9, 17],
+        [218, 2022, "신녕면", "76 - 76", 16, 6, 10],
+        [219, 2022, "신녕면", "77 - 77", 19, 9, 10],
+        [220, 2022, "신녕면", "78 - 78", 28, 4, 24],
+        [221, 2022, "신녕면", "79 - 79", 33, 6, 27],
+        [222, 2022, "신녕면", "80 - 80", 31, 7, 24],
+        [223, 2022, "신녕면", "81 - 81", 26, 7, 19],
+        [224, 2022, "신녕면", "82 - 82", 18, 1, 17],
+        [225, 2022, "신녕면", "83 - 83", 29, 4, 25],
+        [226, 2022, "신녕면", "84 - 84", 23, 1, 22],
+        [227, 2022, "신녕면", "85 - 85", 17, 0, 17],
+        [228, 2022, "신녕면", "86 - 86", 12, 3, 9],
+        [229, 2022, "신녕면", "87 - 87", 14, 2, 12],
+        [230, 2022, "신녕면", "88 - 88", 16, 1, 15],
+        [231, 2022, "신녕면", "89 - 89", 12, 2, 10],
+        [232, 2022, "신녕면", "90 - 90", 20, 1, 19],
+        [233, 2022, "신녕면", "91 - 91", 11, 2, 9],
+        [234, 2022, "신녕면", "92 - 92", 8, 0, 8],
+        [235, 2022, "신녕면", "93 - 93", 6, 0, 6],
+        [236, 2022, "신녕면", "94 - 94", 2, 0, 2],
+        [237, 2022, "신녕면", "96 - 96", 1, 0, 1],
+        [238, 2022, "신녕면", "98 - 98", 2, 0, 2],
+        [239, 2022, "신녕면", "102 - 102", 1, 1, 0],
+        [240, 2022, "화산면", "03-03", 1, 1, 0],
+        [241, 2022, "화산면", "21 - 21", 1, 1, 0],
+        [242, 2022, "화산면", "22 - 22", 3, 3, 0],
+        [243, 2022, "화산면", "23 - 23", 2, 0, 2],
+        [244, 2022, "화산면", "24 - 24", 2, 2, 0],
+        [245, 2022, "화산면", "25 - 25", 4, 3, 1],
+        [246, 2022, "화산면", "26 - 26", 4, 3, 1],
+        [247, 2022, "화산면", "27 - 27", 7, 7, 0],
+        [248, 2022, "화산면", "28 - 28", 3, 3, 0],
+        [249, 2022, "화산면", "29 - 29", 8, 4, 4],
+        [250, 2022, "화산면", "30 - 30", 3, 2, 1],
+        [251, 2022, "화산면", "31 - 31", 3, 2, 1],
+        [252, 2022, "화산면", "32 - 32", 5, 3, 2],
+        [253, 2022, "화산면", "33 - 33", 2, 2, 0],
+        [254, 2022, "화산면", "34 - 34", 4, 4, 0],
+        [255, 2022, "화산면", "35 - 35", 3, 2, 1],
+        [256, 2022, "화산면", "36 - 36", 6, 4, 2],
+        [257, 2022, "화산면", "37 - 37", 4, 4, 0],
+        [258, 2022, "화산면", "38 - 38", 5, 5, 0],
+        [259, 2022, "화산면", "39 - 39", 3, 1, 2],
+        [260, 2022, "화산면", "40 - 40", 9, 8, 1],
+        [261, 2022, "화산면", "41 - 41", 4, 2, 2],
+        [262, 2022, "화산면", "42 - 42", 5, 4, 1],
+        [263, 2022, "화산면", "43 - 43", 5, 4, 1],
+        [264, 2022, "화산면", "44 - 44", 4, 4, 0],
+        [265, 2022, "화산면", "45 - 45", 11, 7, 4],
+        [266, 2022, "화산면", "46 - 46", 13, 8, 5],
+        [267, 2022, "화산면", "47 - 47", 8, 7, 1],
+        [268, 2022, "화산면", "48 - 48", 10, 6, 4],
+        [269, 2022, "화산면", "49 - 49", 14, 10, 4],
+        [270, 2022, "화산면", "50 - 50", 15, 13, 2],
+        [271, 2022, "화산면", "51 - 51", 14, 12, 2],
+        [272, 2022, "화산면", "52 - 52", 14, 13, 1],
+        [273, 2022, "화산면", "53 - 53", 13, 9, 4],
+        [274, 2022, "화산면", "54 - 54", 15, 10, 5],
+        [275, 2022, "화산면", "55 - 55", 17, 13, 4],
+        [276, 2022, "화산면", "56 - 56", 15, 9, 6],
+        [277, 2022, "화산면", "57 - 57", 18, 11, 7],
+        [278, 2022, "화산면", "58 - 58", 28, 21, 7],
+        [279, 2022, "화산면", "59 - 59", 28, 20, 8],
+        [280, 2022, "화산면", "60 - 60", 29, 23, 6],
+        [281, 2022, "화산면", "61 - 61", 19, 14, 5],
+        [282, 2022, "화산면", "62 - 62", 22, 16, 6],
+        [283, 2022, "화산면", "63 - 63", 29, 23, 6],
+        [284, 2022, "화산면", "64 - 64", 25, 17, 8],
+        [285, 2022, "화산면", "65 - 65", 29, 20, 9],
+        [286, 2022, "화산면", "66 - 66", 34, 19, 15],
+        [287, 2022, "화산면", "67 - 67", 23, 14, 9],
+        [288, 2022, "화산면", "68 - 68", 27, 17, 10],
+        [289, 2022, "화산면", "69 - 69", 21, 10, 11],
+        [290, 2022, "화산면", "70 - 70", 20, 8, 12],
+        [291, 2022, "화산면", "71 - 71", 20, 10, 10],
+        [292, 2022, "화산면", "72 - 72", 30, 18, 12],
+        [293, 2022, "화산면", "73 - 73", 22, 11, 11],
+        [294, 2022, "화산면", "74 - 74", 17, 9, 8],
+        [295, 2022, "화산면", "75 - 75", 23, 8, 15],
+        [296, 2022, "화산면", "76 - 76", 8, 0, 8],
+        [297, 2022, "화산면", "77 - 77", 11, 2, 9],
+        [298, 2022, "화산면", "78 - 78", 26, 7, 19],
+        [299, 2022, "화산면", "79 - 79", 20, 2, 18],
+        [300, 2022, "화산면", "80 - 80", 25, 2, 23],
+        [301, 2022, "화산면", "81 - 81", 24, 2, 22],
+        [302, 2022, "화산면", "82 - 82", 18, 3, 15],
+        [303, 2022, "화산면", "83 - 83", 34, 5, 29],
+        [304, 2022, "화산면", "84 - 84", 19, 6, 13],
+        [305, 2022, "화산면", "85 - 85", 26, 3, 23],
+        [306, 2022, "화산면", "86 - 86", 20, 3, 17],
+        [307, 2022, "화산면", "87 - 87", 21, 4, 17],
+        [308, 2022, "화산면", "88 - 88", 20, 5, 15],
+        [309, 2022, "화산면", "89 - 89", 5, 0, 5],
+        [310, 2022, "화산면", "90 - 90", 7, 0, 7],
+        [311, 2022, "화산면", "91 - 91", 7, 0, 7],
+        [312, 2022, "화산면", "92 - 92", 6, 2, 4],
+        [313, 2022, "화산면", "93 - 93", 3, 1, 2],
+        [314, 2022, "화산면", "94 - 94", 3, 0, 3],
+        [315, 2022, "화산면", "95 - 95", 1, 0, 1],
+        [316, 2022, "화산면", "101 - 101", 1, 0, 1],
+        [317, 2022, "화북면", "20 - 20", 1, 0, 1],
+        [318, 2022, "화북면", "21 - 21", 1, 0, 1],
+        [319, 2022, "화북면", "23 - 23", 1, 0, 1],
+        [320, 2022, "화북면", "24 - 24", 2, 2, 0],
+        [321, 2022, "화북면", "25 - 25", 2, 1, 1],
+        [322, 2022, "화북면", "26 - 26", 1, 0, 1],
+        [323, 2022, "화북면", "27 - 27", 1, 1, 0],
+        [324, 2022, "화북면", "28 - 28", 7, 4, 3],
+        [325, 2022, "화북면", "29 - 29", 2, 0, 2],
+        [326, 2022, "화북면", "30 - 30", 1, 1, 0],
+        [327, 2022, "화북면", "32 - 32", 2, 1, 1],
+        [328, 2022, "화북면", "33 - 33", 2, 1, 1],
+        [329, 2022, "화북면", "34 - 34", 1, 1, 0],
+        [330, 2022, "화북면", "35 - 35", 4, 4, 0],
+        [331, 2022, "화북면", "36 - 36", 2, 2, 0],
+        [332, 2022, "화북면", "37 - 37", 3, 1, 2],
+        [333, 2022, "화북면", "38 - 38", 4, 4, 0],
+        [334, 2022, "화북면", "39 - 39", 1, 0, 1],
+        [335, 2022, "화북면", "40 - 40", 2, 2, 0],
+        [336, 2022, "화북면", "41 - 41", 2, 2, 0],
+        [337, 2022, "화북면", "42 - 42", 2, 1, 1],
+        [338, 2022, "화북면", "43 - 43", 6, 5, 1],
+        [339, 2022, "화북면", "44 - 44", 3, 2, 1],
+        [340, 2022, "화북면", "45 - 45", 5, 4, 1],
+        [341, 2022, "화북면", "46 - 46", 8, 6, 2],
+        [342, 2022, "화북면", "47 - 47", 6, 4, 2],
+        [343, 2022, "화북면", "48 - 48", 9, 6, 3],
+        [344, 2022, "화북면", "49 - 49", 11, 8, 3],
+        [345, 2022, "화북면", "50 - 50", 11, 9, 2],
+        [346, 2022, "화북면", "51 - 51", 8, 5, 3],
+        [347, 2022, "화북면", "52 - 52", 12, 8, 4],
+        [348, 2022, "화북면", "53 - 53", 12, 6, 6],
+        [349, 2022, "화북면", "54 - 54", 23, 14, 9],
+        [350, 2022, "화북면", "55 - 55", 24, 15, 9],
+        [351, 2022, "화북면", "56 - 56", 19, 13, 6],
+        [352, 2022, "화북면", "57 - 57", 21, 12, 9],
+        [353, 2022, "화북면", "58 - 58", 19, 13, 6],
+        [354, 2022, "화북면", "59 - 59", 18, 9, 9],
+        [355, 2022, "화북면", "60 - 60", 23, 12, 11],
+        [356, 2022, "화북면", "61 - 61", 22, 13, 9],
+        [357, 2022, "화북면", "62 - 62", 17, 10, 7],
+        [358, 2022, "화북면", "63 - 63", 38, 19, 19],
+        [359, 2022, "화북면", "64 - 64", 10, 7, 3],
+        [360, 2022, "화북면", "65 - 65", 16, 11, 5],
+        [361, 2022, "화북면", "66 - 66", 23, 11, 12],
+        [362, 2022, "화북면", "67 - 67", 15, 11, 4],
+        [363, 2022, "화북면", "68 - 68", 16, 10, 6],
+        [364, 2022, "화북면", "69 - 69", 16, 9, 7],
+        [365, 2022, "화북면", "70 - 70", 15, 12, 3],
+        [366, 2022, "화북면", "71 - 71", 11, 3, 8],
+        [367, 2022, "화북면", "72 - 72", 22, 9, 13],
+        [368, 2022, "화북면", "73 - 73", 20, 13, 7],
+        [369, 2022, "화북면", "74 - 74", 16, 7, 9],
+        [370, 2022, "화북면", "75 - 75", 28, 8, 20],
+        [371, 2022, "화북면", "76 - 76", 7, 4, 3],
+        [372, 2022, "화북면", "77 - 77", 9, 3, 6],
+        [373, 2022, "화북면", "78 - 78", 6, 2, 4],
+        [374, 2022, "화북면", "79 - 79", 14, 2, 12],
+        [375, 2022, "화북면", "80 - 80", 18, 4, 14],
+        [376, 2022, "화북면", "81 - 81", 7, 1, 6],
+        [377, 2022, "화북면", "82 - 82", 18, 3, 15],
+        [378, 2022, "화북면", "83 - 83", 14, 2, 12],
+        [379, 2022, "화북면", "84 - 84", 14, 2, 12],
+        [380, 2022, "화북면", "85 - 85", 14, 1, 13],
+        [381, 2022, "화북면", "86 - 86", 12, 1, 11],
+        [382, 2022, "화북면", "87 - 87", 13, 2, 11],
+        [383, 2022, "화북면", "88 - 88", 13, 4, 9],
+        [384, 2022, "화북면", "89 - 89", 5, 0, 5],
+        [385, 2022, "화북면", "90 - 90", 7, 4, 3],
+        [386, 2022, "화북면", "91 - 91", 9, 1, 8],
+        [387, 2022, "화북면", "92 - 92", 3, 0, 3],
+        [388, 2022, "화북면", "93 - 93", 2, 0, 2],
+        [389, 2022, "화북면", "94 - 94", 5, 0, 5],
+        [390, 2022, "화북면", "95 - 95", 1, 0, 1],
+        [391, 2022, "화북면", "96 - 96", 2, 0, 2],
+        [392, 2022, "화북면", "98 - 98", 1, 0, 1],
+        [393, 2022, "화북면", "100 - 100", 1, 0, 1],
+        [394, 2022, "화북면", "107 - 107", 1, 0, 1],
+        [395, 2022, "화남면", "14 - 14", 1, 1, 0],
+        [396, 2022, "화남면", "17 - 17", 1, 1, 0],
+        [397, 2022, "화남면", "19 - 19", 1, 1, 0],
+        [398, 2022, "화남면", "20 - 20", 1, 1, 0],
+        [399, 2022, "화남면", "23 - 23", 2, 2, 0],
+        [400, 2022, "화남면", "24 - 24", 3, 2, 1],
+        [401, 2022, "화남면", "26 - 26", 3, 3, 0],
+        [402, 2022, "화남면", "27 - 27", 3, 1, 2],
+        [403, 2022, "화남면", "28 - 28", 1, 1, 0],
+        [404, 2022, "화남면", "29 - 29", 5, 4, 1],
+        [405, 2022, "화남면", "30 - 30", 1, 0, 1],
+        [406, 2022, "화남면", "31 - 31", 3, 2, 1],
+        [407, 2022, "화남면", "32 - 32", 4, 4, 0],
+        [408, 2022, "화남면", "33 - 33", 4, 3, 1],
+        [409, 2022, "화남면", "34 - 34", 5, 3, 2],
+        [410, 2022, "화남면", "35 - 35", 3, 2, 1],
+        [411, 2022, "화남면", "36 - 36", 4, 2, 2],
+        [412, 2022, "화남면", "37 - 37", 1, 1, 0],
+        [413, 2022, "화남면", "38 - 38", 5, 4, 1],
+        [414, 2022, "화남면", "39 - 39", 3, 3, 0],
+        [415, 2022, "화남면", "40 - 40", 7, 5, 2],
+        [416, 2022, "화남면", "41 - 41", 7, 7, 0],
+        [417, 2022, "화남면", "42 - 42", 7, 6, 1],
+        [418, 2022, "화남면", "43 - 43", 10, 9, 1],
+        [419, 2022, "화남면", "44 - 44", 6, 5, 1],
+        [420, 2022, "화남면", "45 - 45", 7, 4, 3],
+        [421, 2022, "화남면", "46 - 46", 11, 9, 2],
+        [422, 2022, "화남면", "47 - 47", 7, 3, 4],
+        [423, 2022, "화남면", "48 - 48", 14, 12, 2],
+        [424, 2022, "화남면", "49 - 49", 7, 5, 2],
+        [425, 2022, "화남면", "50 - 50", 19, 14, 5],
+        [426, 2022, "화남면", "51 - 51", 15, 13, 2],
+        [427, 2022, "화남면", "52 - 52", 15, 10, 5],
+        [428, 2022, "화남면", "53 - 53", 16, 8, 8],
+        [429, 2022, "화남면", "54 - 54", 21, 17, 4],
+        [430, 2022, "화남면", "55 - 55", 23, 19, 4],
+        [431, 2022, "화남면", "56 - 56", 23, 13, 10],
+        [432, 2022, "화남면", "57 - 57", 22, 14, 8],
+        [433, 2022, "화남면", "58 - 58", 31, 23, 8],
+        [434, 2022, "화남면", "59 - 59", 25, 20, 5],
+        [435, 2022, "화남면", "60 - 60", 35, 24, 11],
+        [436, 2022, "화남면", "61 - 61", 34, 23, 11],
+        [437, 2022, "화남면", "62 - 62", 28, 17, 11],
+        [438, 2022, "화남면", "63 - 63", 32, 21, 11],
+        [439, 2022, "화남면", "64 - 64", 25, 17, 8],
+        [440, 2022, "화남면", "65 - 65", 27, 18, 9],
+        [441, 2022, "화남면", "66 - 66", 22, 16, 6],
+        [442, 2022, "화남면", "67 - 67", 26, 16, 10],
+        [443, 2022, "화남면", "68 - 68", 19, 10, 9],
+        [444, 2022, "화남면", "69 - 69", 26, 18, 8],
+        [445, 2022, "화남면", "70 - 70", 18, 10, 8],
+        [446, 2022, "화남면", "71 - 71", 22, 12, 10],
+        [447, 2022, "화남면", "72 - 72", 27, 15, 12],
+        [448, 2022, "화남면", "73 - 73", 28, 15, 13],
+        [449, 2022, "화남면", "74 - 74", 21, 6, 15],
+        [450, 2022, "화남면", "75 - 75", 13, 2, 11],
+        [451, 2022, "화남면", "76 - 76", 13, 3, 10],
+        [452, 2022, "화남면", "77 - 77", 14, 6, 8],
+        [453, 2022, "화남면", "78 - 78", 16, 4, 12],
+        [454, 2022, "화남면", "79 - 79", 12, 4, 8],
+        [455, 2022, "화남면", "80 - 80", 26, 4, 22],
+        [456, 2022, "화남면", "81 - 81", 20, 1, 19],
+        [457, 2022, "화남면", "82 - 82", 18, 4, 14],
+        [458, 2022, "화남면", "83 - 83", 22, 2, 20],
+        [459, 2022, "화남면", "84 - 84", 15, 1, 14],
+        [460, 2022, "화남면", "85 - 85", 15, 2, 13],
+        [461, 2022, "화남면", "86 - 86", 14, 3, 11],
+        [462, 2022, "화남면", "87 - 87", 18, 5, 13],
+        [463, 2022, "화남면", "88 - 88", 11, 1, 10],
+        [464, 2022, "화남면", "89 - 89", 6, 0, 6],
+        [465, 2022, "화남면", "90 - 90", 7, 2, 5],
+        [466, 2022, "화남면", "91 - 91", 7, 0, 7],
+        [467, 2022, "화남면", "92 - 92", 4, 0, 4],
+        [468, 2022, "화남면", "93 - 93", 5, 0, 5],
+        [469, 2022, "화남면", "94 - 94", 1, 1, 0],
+        [470, 2022, "화남면", "97 - 97", 2, 1, 1],
+        [471, 2022, "화남면", "101 - 101", 1, 0, 1],
+        [472, 2022, "자양면", "23 - 23", 1, 1, 0],
+        [473, 2022, "자양면", "24 - 24", 1, 0, 1],
+        [474, 2022, "자양면", "25 - 25", 3, 2, 1],
+        [475, 2022, "자양면", "26 - 26", 3, 3, 0],
+        [476, 2022, "자양면", "29 - 29", 1, 0, 1],
+        [477, 2022, "자양면", "30 - 30", 2, 1, 1],
+        [478, 2022, "자양면", "31 - 31", 1, 1, 0],
+        [479, 2022, "자양면", "32 - 32", 3, 2, 1],
+        [480, 2022, "자양면", "33 - 33", 1, 1, 0],
+        [481, 2022, "자양면", "34 - 34", 1, 0, 1],
+        [482, 2022, "자양면", "35 - 35", 1, 1, 0],
+        [483, 2022, "자양면", "36 - 36", 4, 3, 1],
+        [484, 2022, "자양면", "38 - 38", 2, 2, 0],
+        [485, 2022, "자양면", "39 - 39", 2, 0, 2],
+        [486, 2022, "자양면", "40 - 40", 4, 3, 1],
+        [487, 2022, "자양면", "41 - 41", 1, 0, 1],
+        [488, 2022, "자양면", "42 - 42", 2, 1, 1],
+        [489, 2022, "자양면", "43 - 43", 1, 1, 0],
+        [490, 2022, "자양면", "44 - 44", 2, 2, 0],
+        [491, 2022, "자양면", "45 - 45", 1, 0, 1],
+        [492, 2022, "자양면", "47 - 47", 4, 3, 1],
+        [493, 2022, "자양면", "48 - 48", 2, 2, 0],
+        [494, 2022, "자양면", "49 - 49", 6, 3, 3],
+        [495, 2022, "자양면", "50 - 50", 5, 4, 1],
+        [496, 2022, "자양면", "51 - 51", 5, 5, 0],
+        [497, 2022, "자양면", "52 - 52", 4, 3, 1],
+        [498, 2022, "자양면", "53 - 53", 8, 7, 1],
+        [499, 2022, "자양면", "54 - 54", 13, 8, 5],
+        [500, 2022, "자양면", "55 - 55", 12, 7, 5],
+        [501, 2022, "자양면", "56 - 56", 13, 8, 5],
+        [502, 2022, "자양면", "57 - 57", 15, 7, 8],
+        [503, 2022, "자양면", "58 - 58", 17, 9, 8],
+        [504, 2022, "자양면", "59 - 59", 11, 7, 4],
+        [505, 2022, "자양면", "60 - 60", 13, 10, 3],
+        [506, 2022, "자양면", "61 - 61", 14, 10, 4],
+        [507, 2022, "자양면", "62 - 62", 23, 15, 8],
+        [508, 2022, "자양면", "63 - 63", 24, 16, 8],
+        [509, 2022, "자양면", "64 - 64", 8, 6, 2],
+        [510, 2022, "자양면", "65 - 65", 12, 7, 5],
+        [511, 2022, "자양면", "66 - 66", 15, 13, 2],
+        [512, 2022, "자양면", "67 - 67", 10, 8, 2],
+        [513, 2022, "자양면", "68 - 68", 8, 4, 4],
+        [514, 2022, "자양면", "69 - 69", 8, 7, 1],
+        [515, 2022, "자양면", "70 - 70", 8, 5, 3],
+        [516, 2022, "자양면", "71 - 71", 7, 2, 5],
+        [517, 2022, "자양면", "72 - 72", 8, 5, 3],
+        [518, 2022, "자양면", "73 - 73", 11, 5, 6],
+        [519, 2022, "자양면", "74 - 74", 3, 2, 1],
+        [520, 2022, "자양면", "75 - 75", 11, 5, 6],
+        [521, 2022, "자양면", "76 - 76", 12, 6, 6],
+        [522, 2022, "자양면", "77 - 77", 15, 5, 10],
+        [523, 2022, "자양면", "78 - 78", 7, 0, 7],
+        [524, 2022, "자양면", "79 - 79", 8, 0, 8],
+        [525, 2022, "자양면", "80 - 80", 9, 1, 8],
+        [526, 2022, "자양면", "81 - 81", 15, 3, 12],
+        [527, 2022, "자양면", "82 - 82", 4, 0, 4],
+        [528, 2022, "자양면", "83 - 83", 4, 0, 4],
+        [529, 2022, "자양면", "84 - 84", 8, 3, 5],
+        [530, 2022, "자양면", "85 - 85", 10, 3, 7],
+        [531, 2022, "자양면", "86 - 86", 13, 2, 11],
+        [532, 2022, "자양면", "87 - 87", 6, 1, 5],
+        [533, 2022, "자양면", "88 - 88", 5, 0, 5],
+        [534, 2022, "자양면", "89 - 89", 2, 1, 1],
+        [535, 2022, "자양면", "90 - 90", 7, 1, 6],
+        [536, 2022, "자양면", "91 - 91", 2, 2, 0],
+        [537, 2022, "자양면", "92 - 92", 2, 0, 2],
+        [538, 2022, "자양면", "93 - 93", 4, 1, 3],
+        [539, 2022, "자양면", "94 - 94", 2, 0, 2],
+        [540, 2022, "임고면", "19 - 19", 1, 0, 1],
+        [541, 2022, "임고면", "20 - 20", 2, 2, 0],
+        [542, 2022, "임고면", "22 - 22", 4, 1, 3],
+        [543, 2022, "임고면", "23 - 23", 2, 1, 1],
+        [544, 2022, "임고면", "24 - 24", 4, 1, 3],
+        [545, 2022, "임고면", "25 - 25", 4, 2, 2],
+        [546, 2022, "임고면", "26 - 26", 3, 3, 0],
+        [547, 2022, "임고면", "27 - 27", 3, 3, 0],
+        [548, 2022, "임고면", "28 - 28", 10, 10, 0],
+        [549, 2022, "임고면", "29 - 29", 7, 4, 3],
+        [550, 2022, "임고면", "30 - 30", 5, 5, 0],
+        [551, 2022, "임고면", "31 - 31", 2, 1, 1],
+        [552, 2022, "임고면", "32 - 32", 2, 2, 0],
+        [553, 2022, "임고면", "33 - 33", 2, 2, 0],
+        [554, 2022, "임고면", "34 - 34", 6, 3, 3],
+        [555, 2022, "임고면", "35 - 35", 7, 5, 2],
+        [556, 2022, "임고면", "36 - 36", 3, 2, 1],
+        [557, 2022, "임고면", "37 - 37", 2, 2, 0],
+        [558, 2022, "임고면", "38 - 38", 7, 5, 2],
+        [559, 2022, "임고면", "39 - 39", 7, 6, 1],
+        [560, 2022, "임고면", "40 - 40", 7, 6, 1],
+        [561, 2022, "임고면", "41 - 41", 6, 5, 1],
+        [562, 2022, "임고면", "42 - 42", 3, 2, 1],
+        [563, 2022, "임고면", "43 - 43", 8, 4, 4],
+        [564, 2022, "임고면", "44 - 44", 5, 2, 3],
+        [565, 2022, "임고면", "45 - 45", 6, 4, 2],
+        [566, 2022, "임고면", "46 - 46", 10, 7, 3],
+        [567, 2022, "임고면", "47 - 47", 11, 5, 6],
+        [568, 2022, "임고면", "48 - 48", 20, 13, 7],
+        [569, 2022, "임고면", "49 - 49", 8, 7, 1],
+        [570, 2022, "임고면", "50 - 50", 20, 16, 4],
+        [571, 2022, "임고면", "51 - 51", 19, 15, 4],
+        [572, 2022, "임고면", "52 - 52", 25, 13, 12],
+        [573, 2022, "임고면", "53 - 53", 16, 10, 6],
+        [574, 2022, "임고면", "54 - 54", 23, 19, 4],
+        [575, 2022, "임고면", "55 - 55", 30, 16, 14],
+        [576, 2022, "임고면", "56 - 56", 20, 15, 5],
+        [577, 2022, "임고면", "57 - 57", 27, 18, 9],
+        [578, 2022, "임고면", "58 - 58", 30, 20, 10],
+        [579, 2022, "임고면", "59 - 59", 25, 18, 7],
+        [580, 2022, "임고면", "60 - 60", 27, 14, 13],
+        [581, 2022, "임고면", "61 - 61", 27, 18, 9],
+        [582, 2022, "임고면", "62 - 62", 34, 21, 13],
+        [583, 2022, "임고면", "63 - 63", 37, 25, 12],
+        [584, 2022, "임고면", "64 - 64", 29, 19, 10],
+        [585, 2022, "임고면", "65 - 65", 34, 25, 9],
+        [586, 2022, "임고면", "66 - 66", 32, 15, 17],
+        [587, 2022, "임고면", "67 - 67", 36, 22, 14],
+        [588, 2022, "임고면", "68 - 68", 25, 17, 8],
+        [589, 2022, "임고면", "69 - 69", 22, 10, 12],
+        [590, 2022, "임고면", "70 - 70", 27, 13, 14],
+        [591, 2022, "임고면", "71 - 71", 26, 16, 10],
+        [592, 2022, "임고면", "72 - 72", 27, 18, 9],
+        [593, 2022, "임고면", "73 - 73", 31, 10, 21],
+        [594, 2022, "임고면", "74 - 74", 29, 11, 18],
+        [595, 2022, "임고면", "75 - 75", 26, 6, 20],
+        [596, 2022, "임고면", "76 - 76", 14, 4, 10],
+        [597, 2022, "임고면", "77 - 77", 16, 2, 14],
+        [598, 2022, "임고면", "78 - 78", 24, 8, 16],
+        [599, 2022, "임고면", "79 - 79", 21, 3, 18],
+        [600, 2022, "임고면", "80 - 80", 24, 6, 18],
+        [601, 2022, "임고면", "81 - 81", 26, 5, 21],
+        [602, 2022, "임고면", "82 - 82", 22, 8, 14],
+        [603, 2022, "임고면", "83 - 83", 20, 2, 18],
+        [604, 2022, "임고면", "84 - 84", 28, 5, 23],
+        [605, 2022, "임고면", "85 - 85", 18, 1, 17],
+        [606, 2022, "임고면", "86 - 86", 23, 4, 19],
+        [607, 2022, "임고면", "87 - 87", 19, 3, 16],
+        [608, 2022, "임고면", "88 - 88", 15, 3, 12],
+        [609, 2022, "임고면", "89 - 89", 16, 2, 14],
+        [610, 2022, "임고면", "90 - 90", 14, 3, 11],
+        [611, 2022, "임고면", "91 - 91", 4, 2, 2],
+        [612, 2022, "임고면", "92 - 92", 4, 1, 3],
+        [613, 2022, "임고면", "93 - 93", 4, 2, 2],
+        [614, 2022, "임고면", "94 - 94", 4, 1, 3],
+        [615, 2022, "임고면", "95 - 95", 2, 1, 1],
+        [616, 2022, "임고면", "97 - 97", 1, 0, 1],
+        [617, 2022, "임고면", "98 - 98", 1, 0, 1],
+        [618, 2022, "임고면", "101 - 101", 1, 0, 1],
+        [619, 2022, "고경면", "20 - 20", 7, 5, 2],
+        [620, 2022, "고경면", "21 - 21", 72, 67, 5],
+        [621, 2022, "고경면", "22 - 22", 101, 94, 7],
+        [622, 2022, "고경면", "23 - 23", 81, 65, 16],
+        [623, 2022, "고경면", "24 - 24", 57, 51, 6],
+        [624, 2022, "고경면", "25 - 25", 40, 28, 12],
+        [625, 2022, "고경면", "26 - 26", 28, 23, 5],
+        [626, 2022, "고경면", "27 - 27", 19, 19, 0],
+        [627, 2022, "고경면", "28 - 28", 16, 10, 6],
+        [628, 2022, "고경면", "29 - 29", 10, 8, 2],
+        [629, 2022, "고경면", "30 - 30", 9, 5, 4],
+        [630, 2022, "고경면", "31 - 31", 14, 11, 3],
+        [631, 2022, "고경면", "32 - 32", 8, 6, 2],
+        [632, 2022, "고경면", "33 - 33", 10, 9, 1],
+        [633, 2022, "고경면", "34 - 34", 8, 7, 1],
+        [634, 2022, "고경면", "35 - 35", 5, 4, 1],
+        [635, 2022, "고경면", "36 - 36", 8, 8, 0],
+        [636, 2022, "고경면", "37 - 37", 6, 4, 2],
+        [637, 2022, "고경면", "38 - 38", 10, 8, 2],
+        [638, 2022, "고경면", "39 - 39", 14, 11, 3],
+        [639, 2022, "고경면", "40 - 40", 9, 7, 2],
+        [640, 2022, "고경면", "41 - 41", 15, 11, 4],
+        [641, 2022, "고경면", "42 - 42", 11, 8, 3],
+        [642, 2022, "고경면", "43 - 43", 7, 5, 2],
+        [643, 2022, "고경면", "44 - 44", 9, 6, 3],
+        [644, 2022, "고경면", "45 - 45", 17, 11, 6],
+        [645, 2022, "고경면", "46 - 46", 14, 11, 3],
+        [646, 2022, "고경면", "47 - 47", 19, 14, 5],
+        [647, 2022, "고경면", "48 - 48", 14, 12, 2],
+        [648, 2022, "고경면", "49 - 49", 15, 12, 3],
+        [649, 2022, "고경면", "50 - 50", 21, 15, 6],
+        [650, 2022, "고경면", "51 - 51", 32, 24, 8],
+        [651, 2022, "고경면", "52 - 52", 33, 24, 9],
+        [652, 2022, "고경면", "53 - 53", 34, 24, 10],
+        [653, 2022, "고경면", "54 - 54", 26, 18, 8],
+        [654, 2022, "고경면", "55 - 55", 36, 21, 15],
+        [655, 2022, "고경면", "56 - 56", 30, 21, 9],
+        [656, 2022, "고경면", "57 - 57", 46, 28, 18],
+        [657, 2022, "고경면", "58 - 58", 40, 21, 19],
+        [658, 2022, "고경면", "59 - 59", 47, 33, 14],
+        [659, 2022, "고경면", "60 - 60", 48, 34, 14],
+        [660, 2022, "고경면", "61 - 61", 52, 28, 24],
+        [661, 2022, "고경면", "62 - 62", 36, 24, 12],
+        [662, 2022, "고경면", "63 - 63", 58, 43, 15],
+        [663, 2022, "고경면", "64 - 64", 43, 37, 6],
+        [664, 2022, "고경면", "65 - 65", 36, 21, 15],
+        [665, 2022, "고경면", "66 - 66", 45, 33, 12],
+        [666, 2022, "고경면", "67 - 67", 51, 25, 26],
+        [667, 2022, "고경면", "68 - 68", 36, 20, 16],
+        [668, 2022, "고경면", "69 - 69", 24, 10, 14],
+        [669, 2022, "고경면", "70 - 70", 32, 21, 11],
+        [670, 2022, "고경면", "71 - 71", 30, 16, 14],
+        [671, 2022, "고경면", "72 - 72", 30, 17, 13],
+        [672, 2022, "고경면", "73 - 73", 33, 18, 15],
+        [673, 2022, "고경면", "74 - 74", 28, 9, 19],
+        [674, 2022, "고경면", "75 - 75", 33, 8, 25],
+        [675, 2022, "고경면", "76 - 76", 22, 4, 18],
+        [676, 2022, "고경면", "77 - 77", 29, 9, 20],
+        [677, 2022, "고경면", "78 - 78", 28, 9, 19],
+        [678, 2022, "고경면", "79 - 79", 27, 8, 19],
+        [679, 2022, "고경면", "80 - 80", 55, 16, 39],
+        [680, 2022, "고경면", "81 - 81", 37, 11, 26],
+        [681, 2022, "고경면", "82 - 82", 31, 5, 26],
+        [682, 2022, "고경면", "83 - 83", 36, 4, 32],
+        [683, 2022, "고경면", "84 - 84", 22, 6, 16],
+        [684, 2022, "고경면", "85 - 85", 16, 4, 12],
+        [685, 2022, "고경면", "86 - 86", 34, 5, 29],
+        [686, 2022, "고경면", "87 - 87", 23, 2, 21],
+        [687, 2022, "고경면", "88 - 88", 26, 3, 23],
+        [688, 2022, "고경면", "89 - 89", 14, 4, 10],
+        [689, 2022, "고경면", "90 - 90", 13, 1, 12],
+        [690, 2022, "고경면", "91 - 91", 9, 0, 9],
+        [691, 2022, "고경면", "92 - 92", 2, 0, 2],
+        [692, 2022, "고경면", "93 - 93", 7, 0, 7],
+        [693, 2022, "고경면", "94 - 94", 4, 0, 4],
+        [694, 2022, "고경면", "95 - 95", 1, 1, 0],
+        [695, 2022, "고경면", "96 - 96", 1, 0, 1],
+        [696, 2022, "고경면", "97 - 97", 1, 0, 1],
+        [697, 2022, "고경면", "101 - 101", 1, 0, 1],
+        [698, 2022, "북안면", "21 - 21", 2, 0, 2],
+        [699, 2022, "북안면", "22 - 22", 3, 3, 0],
+        [700, 2022, "북안면", "23 - 23", 6, 5, 1],
+        [701, 2022, "북안면", "24 - 24", 4, 3, 1],
+        [702, 2022, "북안면", "25 - 25", 10, 4, 6],
+        [703, 2022, "북안면", "26 - 26", 11, 6, 5],
+        [704, 2022, "북안면", "27 - 27", 5, 2, 3],
+        [705, 2022, "북안면", "28 - 28", 8, 4, 4],
+        [706, 2022, "북안면", "29 - 29", 5, 3, 2],
+        [707, 2022, "북안면", "30 - 30", 2, 2, 0],
+        [708, 2022, "북안면", "31 - 31", 7, 6, 1],
+        [709, 2022, "북안면", "32 - 32", 8, 5, 3],
+        [710, 2022, "북안면", "33 - 33", 4, 2, 2],
+        [711, 2022, "북안면", "34 - 34", 6, 4, 2],
+        [712, 2022, "북안면", "35 - 35", 7, 6, 1],
+        [713, 2022, "북안면", "36 - 36", 7, 5, 2],
+        [714, 2022, "북안면", "37 - 37", 5, 5, 0],
+        [715, 2022, "북안면", "38 - 38", 8, 6, 2],
+        [716, 2022, "북안면", "39 - 39", 5, 4, 1],
+        [717, 2022, "북안면", "40 - 40", 5, 4, 1],
+        [718, 2022, "북안면", "41 - 41", 12, 11, 1],
+        [719, 2022, "북안면", "42 - 42", 9, 5, 4],
+        [720, 2022, "북안면", "43 - 43", 13, 9, 4],
+        [721, 2022, "북안면", "44 - 44", 13, 9, 4],
+        [722, 2022, "북안면", "45 - 45", 10, 6, 4],
+        [723, 2022, "북안면", "46 - 46", 15, 10, 5],
+        [724, 2022, "북안면", "47 - 47", 5, 3, 2],
+        [725, 2022, "북안면", "48 - 48", 19, 16, 3],
+        [726, 2022, "북안면", "49 - 49", 20, 9, 11],
+        [727, 2022, "북안면", "50 - 50", 15, 12, 3],
+        [728, 2022, "북안면", "51 - 51", 23, 14, 9],
+        [729, 2022, "북안면", "52 - 52", 24, 20, 4],
+        [730, 2022, "북안면", "53 - 53", 18, 13, 5],
+        [731, 2022, "북안면", "54 - 54", 33, 21, 12],
+        [732, 2022, "북안면", "55 - 55", 33, 19, 14],
+        [733, 2022, "북안면", "56 - 56", 36, 29, 7],
+        [734, 2022, "북안면", "57 - 57", 27, 22, 5],
+        [735, 2022, "북안면", "58 - 58", 32, 21, 11],
+        [736, 2022, "북안면", "59 - 59", 42, 27, 15],
+        [737, 2022, "북안면", "60 - 60", 36, 26, 10],
+        [738, 2022, "북안면", "61 - 61", 56, 37, 19],
+        [739, 2022, "북안면", "62 - 62", 42, 24, 18],
+        [740, 2022, "북안면", "63 - 63", 55, 38, 17],
+        [741, 2022, "북안면", "64 - 64", 35, 26, 9],
+        [742, 2022, "북안면", "65 - 65", 49, 34, 15],
+        [743, 2022, "북안면", "66 - 66", 45, 30, 15],
+        [744, 2022, "북안면", "67 - 67", 45, 26, 19],
+        [745, 2022, "북안면", "68 - 68", 36, 24, 12],
+        [746, 2022, "북안면", "69 - 69", 32, 19, 13],
+        [747, 2022, "북안면", "70 - 70", 37, 20, 17],
+        [748, 2022, "북안면", "71 - 71", 33, 19, 14],
+        [749, 2022, "북안면", "72 - 72", 29, 13, 16],
+        [750, 2022, "북안면", "73 - 73", 25, 14, 11],
+        [751, 2022, "북안면", "74 - 74", 23, 6, 17],
+        [752, 2022, "북안면", "75 - 75", 32, 18, 14],
+        [753, 2022, "북안면", "76 - 76", 23, 12, 11],
+        [754, 2022, "북안면", "77 - 77", 19, 9, 10],
+        [755, 2022, "북안면", "78 - 78", 26, 3, 23],
+        [756, 2022, "북안면", "79 - 79", 22, 7, 15],
+        [757, 2022, "북안면", "80 - 80", 33, 10, 23],
+        [758, 2022, "북안면", "81 - 81", 30, 5, 25],
+        [759, 2022, "북안면", "82 - 82", 27, 4, 23],
+        [760, 2022, "북안면", "83 - 83", 37, 8, 29],
+        [761, 2022, "북안면", "84 - 84", 20, 3, 17],
+        [762, 2022, "북안면", "85 - 85", 34, 4, 30],
+        [763, 2022, "북안면", "86 - 86", 29, 4, 25],
+        [764, 2022, "북안면", "87 - 87", 23, 2, 21],
+        [765, 2022, "북안면", "88 - 88", 18, 5, 13],
+        [766, 2022, "북안면", "89 - 89", 17, 3, 14],
+        [767, 2022, "북안면", "90 - 90", 12, 1, 11],
+        [768, 2022, "북안면", "91 - 91", 8, 1, 7],
+        [769, 2022, "북안면", "92 - 92", 10, 1, 9],
+        [770, 2022, "북안면", "93 - 93", 4, 0, 4],
+        [771, 2022, "북안면", "94 - 94", 4, 2, 2],
+        [772, 2022, "북안면", "96 - 96", 1, 1, 0],
+        [773, 2022, "북안면", "98 - 98", 2, 0, 2],
+        [774, 2022, "대창면", "19 - 19", 2, 2, 0],
+        [775, 2022, "대창면", "20 - 20", 2, 1, 1],
+        [776, 2022, "대창면", "22 - 22", 4, 3, 1],
+        [777, 2022, "대창면", "23 - 23", 5, 3, 2],
+        [778, 2022, "대창면", "24 - 24", 1, 1, 0],
+        [779, 2022, "대창면", "26 - 26", 3, 3, 0],
+        [780, 2022, "대창면", "27 - 27", 7, 4, 3],
+        [781, 2022, "대창면", "28 - 28", 6, 4, 2],
+        [782, 2022, "대창면", "29 - 29", 4, 3, 1],
+        [783, 2022, "대창면", "30 - 30", 6, 4, 2],
+        [784, 2022, "대창면", "31 - 31", 4, 2, 2],
+        [785, 2022, "대창면", "32 - 32", 4, 3, 1],
+        [786, 2022, "대창면", "33 - 33", 5, 2, 3],
+        [787, 2022, "대창면", "34 - 34", 5, 3, 2],
+        [788, 2022, "대창면", "35 - 35", 3, 2, 1],
+        [789, 2022, "대창면", "36 - 36", 3, 2, 1],
+        [790, 2022, "대창면", "37 - 37", 9, 7, 2],
+        [791, 2022, "대창면", "38 - 38", 5, 5, 0],
+        [792, 2022, "대창면", "39 - 39", 6, 5, 1],
+        [793, 2022, "대창면", "40 - 40", 13, 8, 5],
+        [794, 2022, "대창면", "41 - 41", 8, 8, 0],
+        [795, 2022, "대창면", "42 - 42", 11, 7, 4],
+        [796, 2022, "대창면", "43 - 43", 6, 4, 2],
+        [797, 2022, "대창면", "44 - 44", 3, 0, 3],
+        [798, 2022, "대창면", "45 - 45", 3, 2, 1],
+        [799, 2022, "대창면", "46 - 46", 13, 11, 2],
+        [800, 2022, "대창면", "47 - 47", 9, 9, 0],
+        [801, 2022, "대창면", "48 - 48", 11, 8, 3],
+        [802, 2022, "대창면", "49 - 49", 13, 10, 3],
+        [803, 2022, "대창면", "50 - 50", 17, 14, 3],
+        [804, 2022, "대창면", "51 - 51", 20, 18, 2],
+        [805, 2022, "대창면", "52 - 52", 19, 12, 7],
+        [806, 2022, "대창면", "53 - 53", 22, 17, 5],
+        [807, 2022, "대창면", "54 - 54", 24, 16, 8],
+        [808, 2022, "대창면", "55 - 55", 19, 13, 6],
+        [809, 2022, "대창면", "56 - 56", 21, 19, 2],
+        [810, 2022, "대창면", "57 - 57", 20, 16, 4],
+        [811, 2022, "대창면", "58 - 58", 25, 21, 4],
+        [812, 2022, "대창면", "59 - 59", 24, 22, 2],
+        [813, 2022, "대창면", "60 - 60", 35, 23, 12],
+        [814, 2022, "대창면", "61 - 61", 32, 23, 9],
+        [815, 2022, "대창면", "62 - 62", 36, 23, 13],
+        [816, 2022, "대창면", "63 - 63", 33, 21, 12],
+        [817, 2022, "대창면", "64 - 64", 27, 13, 14],
+        [818, 2022, "대창면", "65 - 65", 35, 24, 11],
+        [819, 2022, "대창면", "66 - 66", 34, 20, 14],
+        [820, 2022, "대창면", "67 - 67", 30, 23, 7],
+        [821, 2022, "대창면", "68 - 68", 30, 17, 13],
+        [822, 2022, "대창면", "69 - 69", 17, 13, 4],
+        [823, 2022, "대창면", "70 - 70", 32, 21, 11],
+        [824, 2022, "대창면", "71 - 71", 26, 10, 16],
+        [825, 2022, "대창면", "72 - 72", 19, 9, 10],
+        [826, 2022, "대창면", "73 - 73", 23, 11, 12],
+        [827, 2022, "대창면", "74 - 74", 23, 9, 14],
+        [828, 2022, "대창면", "75 - 75", 18, 10, 8],
+        [829, 2022, "대창면", "76 - 76", 8, 2, 6],
+        [830, 2022, "대창면", "77 - 77", 12, 3, 9],
+        [831, 2022, "대창면", "78 - 78", 12, 4, 8],
+        [832, 2022, "대창면", "79 - 79", 19, 3, 16],
+        [833, 2022, "대창면", "80 - 80", 29, 6, 23],
+        [834, 2022, "대창면", "81 - 81", 23, 5, 18],
+        [835, 2022, "대창면", "82 - 82", 15, 6, 9],
+        [836, 2022, "대창면", "83 - 83", 15, 2, 13],
+        [837, 2022, "대창면", "84 - 84", 16, 3, 13],
+        [838, 2022, "대창면", "85 - 85", 13, 3, 10],
+        [839, 2022, "대창면", "86 - 86", 15, 3, 12],
+        [840, 2022, "대창면", "87 - 87", 12, 1, 11],
+        [841, 2022, "대창면", "88 - 88", 10, 0, 10],
+        [842, 2022, "대창면", "89 - 89", 10, 1, 9],
+        [843, 2022, "대창면", "90 - 90", 5, 0, 5],
+        [844, 2022, "대창면", "91 - 91", 2, 1, 1],
+        [845, 2022, "대창면", "92 - 92", 4, 1, 3],
+        [846, 2022, "대창면", "93 - 93", 2, 1, 1],
+        [847, 2022, "대창면", "94 - 94", 1, 0, 1],
+        [848, 2022, "대창면", "95 - 95", 1, 0, 1],
+        [849, 2022, "대창면", "98 - 98", 1, 0, 1],
+        [850, 2022, "대창면", "100 - 100", 1, 0, 1],
+        [851, 2022, "동부동", "04-04", 1, 1, 0],
+        [852, 2022, "동부동", "05-05", 1, 0, 1],
+        [853, 2022, "동부동", "12-12", 1, 0, 1],
+        [854, 2022, "동부동", "13 - 13", 1, 0, 1],
+        [855, 2022, "동부동", "16 - 16", 1, 0, 1],
+        [856, 2022, "동부동", "18 - 18", 1, 0, 1],
+        [857, 2022, "동부동", "19 - 19", 4, 3, 1],
+        [858, 2022, "동부동", "20 - 20", 7, 5, 2],
+        [859, 2022, "동부동", "21 - 21", 12, 8, 4],
+        [860, 2022, "동부동", "22 - 22", 23, 15, 8],
+        [861, 2022, "동부동", "23 - 23", 24, 13, 11],
+        [862, 2022, "동부동", "24 - 24", 35, 22, 13],
+        [863, 2022, "동부동", "25 - 25", 47, 25, 22],
+        [864, 2022, "동부동", "26 - 26", 75, 47, 28],
+        [865, 2022, "동부동", "27 - 27", 75, 41, 34],
+        [866, 2022, "동부동", "28 - 28", 86, 53, 33],
+        [867, 2022, "동부동", "29 - 29", 87, 57, 30],
+        [868, 2022, "동부동", "30 - 30", 84, 54, 30],
+        [869, 2022, "동부동", "31 - 31", 83, 60, 23],
+        [870, 2022, "동부동", "32 - 32", 77, 55, 22],
+        [871, 2022, "동부동", "33 - 33", 69, 50, 19],
+        [872, 2022, "동부동", "34 - 34", 64, 40, 24],
+        [873, 2022, "동부동", "35 - 35", 52, 32, 20],
+        [874, 2022, "동부동", "36 - 36", 60, 46, 14],
+        [875, 2022, "동부동", "37 - 37", 61, 47, 14],
+        [876, 2022, "동부동", "38 - 38", 67, 46, 21],
+        [877, 2022, "동부동", "39 - 39", 63, 44, 19],
+        [878, 2022, "동부동", "40 - 40", 64, 55, 9],
+        [879, 2022, "동부동", "41 - 41", 84, 58, 26],
+        [880, 2022, "동부동", "42 - 42", 74, 54, 20],
+        [881, 2022, "동부동", "43 - 43", 83, 57, 26],
+        [882, 2022, "동부동", "44 - 44", 58, 42, 16],
+        [883, 2022, "동부동", "45 - 45", 56, 32, 24],
+        [884, 2022, "동부동", "46 - 46", 68, 44, 24],
+        [885, 2022, "동부동", "47 - 47", 87, 61, 26],
+        [886, 2022, "동부동", "48 - 48", 94, 53, 41],
+        [887, 2022, "동부동", "49 - 49", 76, 40, 36],
+        [888, 2022, "동부동", "50 - 50", 74, 45, 29],
+        [889, 2022, "동부동", "51 - 51", 101, 51, 50],
+        [890, 2022, "동부동", "52 - 52", 98, 56, 42],
+        [891, 2022, "동부동", "53 - 53", 106, 56, 50],
+        [892, 2022, "동부동", "54 - 54", 116, 60, 56],
+        [893, 2022, "동부동", "55 - 55", 126, 68, 58],
+        [894, 2022, "동부동", "56 - 56", 115, 66, 49],
+        [895, 2022, "동부동", "57 - 57", 108, 47, 61],
+        [896, 2022, "동부동", "58 - 58", 121, 61, 60],
+        [897, 2022, "동부동", "59 - 59", 111, 48, 63],
+        [898, 2022, "동부동", "60 - 60", 108, 45, 63],
+        [899, 2022, "동부동", "61 - 61", 155, 77, 78],
+        [900, 2022, "동부동", "62 - 62", 144, 62, 82],
+        [901, 2022, "동부동", "63 - 63", 151, 55, 96],
+        [902, 2022, "동부동", "64 - 64", 121, 45, 76],
+        [903, 2022, "동부동", "65 - 65", 114, 56, 58],
+        [904, 2022, "동부동", "66 - 66", 115, 40, 75],
+        [905, 2022, "동부동", "67 - 67", 104, 37, 67],
+        [906, 2022, "동부동", "68 - 68", 107, 37, 70],
+        [907, 2022, "동부동", "69 - 69", 88, 32, 56],
+        [908, 2022, "동부동", "70 - 70", 92, 26, 66],
+        [909, 2022, "동부동", "71 - 71", 78, 23, 55],
+        [910, 2022, "동부동", "72 - 72", 81, 28, 53],
+        [911, 2022, "동부동", "73 - 73", 66, 17, 49],
+        [912, 2022, "동부동", "74 - 74", 91, 24, 67],
+        [913, 2022, "동부동", "75 - 75", 74, 16, 58],
+        [914, 2022, "동부동", "76 - 76", 50, 18, 32],
+        [915, 2022, "동부동", "77 - 77", 56, 17, 39],
+        [916, 2022, "동부동", "78 - 78", 51, 11, 40],
+        [917, 2022, "동부동", "79 - 79", 70, 11, 59],
+        [918, 2022, "동부동", "80 - 80", 74, 12, 62],
+        [919, 2022, "동부동", "81 - 81", 54, 14, 40],
+        [920, 2022, "동부동", "82 - 82", 68, 9, 59],
+        [921, 2022, "동부동", "83 - 83", 56, 10, 46],
+        [922, 2022, "동부동", "84 - 84", 66, 11, 55],
+        [923, 2022, "동부동", "85 - 85", 53, 7, 46],
+        [924, 2022, "동부동", "86 - 86", 52, 9, 43],
+        [925, 2022, "동부동", "87 - 87", 37, 5, 32],
+        [926, 2022, "동부동", "88 - 88", 38, 4, 34],
+        [927, 2022, "동부동", "89 - 89", 18, 1, 17],
+        [928, 2022, "동부동", "90 - 90", 24, 4, 20],
+        [929, 2022, "동부동", "91 - 91", 13, 1, 12],
+        [930, 2022, "동부동", "92 - 92", 9, 2, 7],
+        [931, 2022, "동부동", "93 - 93", 8, 1, 7],
+        [932, 2022, "동부동", "94 - 94", 7, 1, 6],
+        [933, 2022, "동부동", "95 - 95", 3, 0, 3],
+        [934, 2022, "동부동", "96 - 96", 1, 0, 1],
+        [935, 2022, "동부동", "98 - 98", 1, 0, 1],
+        [936, 2022, "동부동", "99 - 99", 1, 0, 1],
+        [937, 2022, "중앙동", "08-08", 1, 1, 0],
+        [938, 2022, "중앙동", "14 - 14", 1, 0, 1],
+        [939, 2022, "중앙동", "15 - 15", 1, 0, 1],
+        [940, 2022, "중앙동", "18 - 18", 2, 1, 1],
+        [941, 2022, "중앙동", "19 - 19", 3, 1, 2],
+        [942, 2022, "중앙동", "20 - 20", 6, 4, 2],
+        [943, 2022, "중앙동", "21 - 21", 10, 4, 6],
+        [944, 2022, "중앙동", "22 - 22", 12, 7, 5],
+        [945, 2022, "중앙동", "23 - 23", 17, 7, 10],
+        [946, 2022, "중앙동", "24 - 24", 30, 13, 17],
+        [947, 2022, "중앙동", "25 - 25", 26, 13, 13],
+        [948, 2022, "중앙동", "26 - 26", 43, 30, 13],
+        [949, 2022, "중앙동", "27 - 27", 48, 30, 18],
+        [950, 2022, "중앙동", "28 - 28", 47, 33, 14],
+        [951, 2022, "중앙동", "29 - 29", 39, 26, 13],
+        [952, 2022, "중앙동", "30 - 30", 50, 36, 14],
+        [953, 2022, "중앙동", "31 - 31", 41, 25, 16],
+        [954, 2022, "중앙동", "32 - 32", 34, 27, 7],
+        [955, 2022, "중앙동", "33 - 33", 30, 26, 4],
+        [956, 2022, "중앙동", "34 - 34", 28, 23, 5],
+        [957, 2022, "중앙동", "35 - 35", 37, 25, 12],
+        [958, 2022, "중앙동", "36 - 36", 24, 18, 6],
+        [959, 2022, "중앙동", "37 - 37", 16, 10, 6],
+        [960, 2022, "중앙동", "38 - 38", 20, 13, 7],
+        [961, 2022, "중앙동", "39 - 39", 26, 20, 6],
+        [962, 2022, "중앙동", "40 - 40", 21, 13, 8],
+        [963, 2022, "중앙동", "41 - 41", 30, 14, 16],
+        [964, 2022, "중앙동", "42 - 42", 30, 19, 11],
+        [965, 2022, "중앙동", "43 - 43", 19, 16, 3],
+        [966, 2022, "중앙동", "44 - 44", 27, 22, 5],
+        [967, 2022, "중앙동", "45 - 45", 18, 13, 5],
+        [968, 2022, "중앙동", "46 - 46", 30, 20, 10],
+        [969, 2022, "중앙동", "47 - 47", 40, 27, 13],
+        [970, 2022, "중앙동", "48 - 48", 28, 15, 13],
+        [971, 2022, "중앙동", "49 - 49", 38, 22, 16],
+        [972, 2022, "중앙동", "50 - 50", 35, 26, 9],
+        [973, 2022, "중앙동", "51 - 51", 39, 23, 16],
+        [974, 2022, "중앙동", "52 - 52", 36, 21, 15],
+        [975, 2022, "중앙동", "53 - 53", 27, 15, 12],
+        [976, 2022, "중앙동", "54 - 54", 38, 23, 15],
+        [977, 2022, "중앙동", "55 - 55", 59, 39, 20],
+        [978, 2022, "중앙동", "56 - 56", 46, 24, 22],
+        [979, 2022, "중앙동", "57 - 57", 33, 19, 14],
+        [980, 2022, "중앙동", "58 - 58", 34, 16, 18],
+        [981, 2022, "중앙동", "59 - 59", 35, 20, 15],
+        [982, 2022, "중앙동", "60 - 60", 52, 27, 25],
+        [983, 2022, "중앙동", "61 - 61", 43, 23, 20],
+        [984, 2022, "중앙동", "62 - 62", 58, 29, 29],
+        [985, 2022, "중앙동", "63 - 63", 46, 29, 17],
+        [986, 2022, "중앙동", "64 - 64", 29, 16, 13],
+        [987, 2022, "중앙동", "65 - 65", 40, 22, 18],
+        [988, 2022, "중앙동", "66 - 66", 44, 23, 21],
+        [989, 2022, "중앙동", "67 - 67", 27, 12, 15],
+        [990, 2022, "중앙동", "68 - 68", 23, 13, 10],
+        [991, 2022, "중앙동", "69 - 69", 29, 19, 10],
+        [992, 2022, "중앙동", "70 - 70", 27, 13, 14],
+        [993, 2022, "중앙동", "71 - 71", 19, 5, 14],
+        [994, 2022, "중앙동", "72 - 72", 28, 11, 17],
+        [995, 2022, "중앙동", "73 - 73", 27, 11, 16],
+        [996, 2022, "중앙동", "74 - 74", 31, 8, 23],
+        [997, 2022, "중앙동", "75 - 75", 22, 5, 17],
+        [998, 2022, "중앙동", "76 - 76", 32, 6, 26],
+        [999, 2022, "중앙동", "77 - 77", 20, 4, 16],
+        [1000, 2022, "중앙동", "78 - 78", 13, 4, 9],
+        [1001, 2022, "중앙동", "79 - 79", 28, 6, 22],
+        [1002, 2022, "중앙동", "80 - 80", 25, 4, 21],
+        [1003, 2022, "중앙동", "81 - 81", 20, 3, 17],
+        [1004, 2022, "중앙동", "82 - 82", 19, 3, 16],
+        [1005, 2022, "중앙동", "83 - 83", 22, 5, 17],
+        [1006, 2022, "중앙동", "84 - 84", 20, 4, 16],
+        [1007, 2022, "중앙동", "85 - 85", 11, 2, 9],
+        [1008, 2022, "중앙동", "86 - 86", 17, 6, 11],
+        [1009, 2022, "중앙동", "87 - 87", 9, 3, 6],
+        [1010, 2022, "중앙동", "88 - 88", 11, 3, 8],
+        [1011, 2022, "중앙동", "89 - 89", 10, 3, 7],
+        [1012, 2022, "중앙동", "90 - 90", 9, 3, 6],
+        [1013, 2022, "중앙동", "91 - 91", 4, 0, 4],
+        [1014, 2022, "중앙동", "92 - 92", 5, 1, 4],
+        [1015, 2022, "중앙동", "93 - 93", 6, 0, 6],
+        [1016, 2022, "중앙동", "96 - 96", 1, 0, 1],
+        [1017, 2022, "중앙동", "97 - 97", 1, 0, 1],
+        [1018, 2022, "서부동", "15 - 15", 1, 1, 0],
+        [1019, 2022, "서부동", "16 - 16", 6, 0, 6],
+        [1020, 2022, "서부동", "17 - 17", 10, 0, 10],
+        [1021, 2022, "서부동", "18 - 18", 2, 0, 2],
+        [1022, 2022, "서부동", "19 - 19", 2, 2, 0],
+        [1023, 2022, "서부동", "20 - 20", 8, 7, 1],
+        [1024, 2022, "서부동", "21 - 21", 10, 6, 4],
+        [1025, 2022, "서부동", "22 - 22", 6, 4, 2],
+        [1026, 2022, "서부동", "23 - 23", 14, 7, 7],
+        [1027, 2022, "서부동", "24 - 24", 12, 7, 5],
+        [1028, 2022, "서부동", "25 - 25", 14, 11, 3],
+        [1029, 2022, "서부동", "26 - 26", 12, 4, 8],
+        [1030, 2022, "서부동", "27 - 27", 15, 9, 6],
+        [1031, 2022, "서부동", "28 - 28", 19, 12, 7],
+        [1032, 2022, "서부동", "29 - 29", 11, 8, 3],
+        [1033, 2022, "서부동", "30 - 30", 11, 4, 7],
+        [1034, 2022, "서부동", "31 - 31", 15, 8, 7],
+        [1035, 2022, "서부동", "32 - 32", 17, 13, 4],
+        [1036, 2022, "서부동", "33 - 33", 9, 5, 4],
+        [1037, 2022, "서부동", "34 - 34", 10, 6, 4],
+        [1038, 2022, "서부동", "35 - 35", 7, 4, 3],
+        [1039, 2022, "서부동", "36 - 36", 11, 4, 7],
+        [1040, 2022, "서부동", "37 - 37", 14, 11, 3],
+        [1041, 2022, "서부동", "38 - 38", 13, 11, 2],
+        [1042, 2022, "서부동", "39 - 39", 16, 14, 2],
+        [1043, 2022, "서부동", "40 - 40", 15, 12, 3],
+        [1044, 2022, "서부동", "41 - 41", 14, 7, 7],
+        [1045, 2022, "서부동", "42 - 42", 18, 14, 4],
+        [1046, 2022, "서부동", "43 - 43", 11, 10, 1],
+        [1047, 2022, "서부동", "44 - 44", 10, 8, 2],
+        [1048, 2022, "서부동", "45 - 45", 14, 12, 2],
+        [1049, 2022, "서부동", "46 - 46", 16, 13, 3],
+        [1050, 2022, "서부동", "47 - 47", 23, 17, 6],
+        [1051, 2022, "서부동", "48 - 48", 30, 22, 8],
+        [1052, 2022, "서부동", "49 - 49", 16, 12, 4],
+        [1053, 2022, "서부동", "50 - 50", 15, 11, 4],
+        [1054, 2022, "서부동", "51 - 51", 32, 25, 7],
+        [1055, 2022, "서부동", "52 - 52", 19, 14, 5],
+        [1056, 2022, "서부동", "53 - 53", 41, 25, 16],
+        [1057, 2022, "서부동", "54 - 54", 27, 17, 10],
+        [1058, 2022, "서부동", "55 - 55", 38, 25, 13],
+        [1059, 2022, "서부동", "56 - 56", 35, 24, 11],
+        [1060, 2022, "서부동", "57 - 57", 43, 27, 16],
+        [1061, 2022, "서부동", "58 - 58", 41, 26, 15],
+        [1062, 2022, "서부동", "59 - 59", 27, 17, 10],
+        [1063, 2022, "서부동", "60 - 60", 42, 30, 12],
+        [1064, 2022, "서부동", "61 - 61", 37, 20, 17],
+        [1065, 2022, "서부동", "62 - 62", 32, 19, 13],
+        [1066, 2022, "서부동", "63 - 63", 34, 15, 19],
+        [1067, 2022, "서부동", "64 - 64", 34, 14, 20],
+        [1068, 2022, "서부동", "65 - 65", 33, 21, 12],
+        [1069, 2022, "서부동", "66 - 66", 41, 21, 20],
+        [1070, 2022, "서부동", "67 - 67", 35, 24, 11],
+        [1071, 2022, "서부동", "68 - 68", 19, 10, 9],
+        [1072, 2022, "서부동", "69 - 69", 33, 17, 16],
+        [1073, 2022, "서부동", "70 - 70", 23, 10, 13],
+        [1074, 2022, "서부동", "71 - 71", 32, 13, 19],
+        [1075, 2022, "서부동", "72 - 72", 33, 14, 19],
+        [1076, 2022, "서부동", "73 - 73", 28, 12, 16],
+        [1077, 2022, "서부동", "74 - 74", 27, 12, 15],
+        [1078, 2022, "서부동", "75 - 75", 29, 10, 19],
+        [1079, 2022, "서부동", "76 - 76", 9, 2, 7],
+        [1080, 2022, "서부동", "77 - 77", 17, 4, 13],
+        [1081, 2022, "서부동", "78 - 78", 33, 7, 26],
+        [1082, 2022, "서부동", "79 - 79", 24, 4, 20],
+        [1083, 2022, "서부동", "80 - 80", 31, 5, 26],
+        [1084, 2022, "서부동", "81 - 81", 18, 0, 18],
+        [1085, 2022, "서부동", "82 - 82", 14, 3, 11],
+        [1086, 2022, "서부동", "83 - 83", 15, 2, 13],
+        [1087, 2022, "서부동", "84 - 84", 18, 3, 15],
+        [1088, 2022, "서부동", "85 - 85", 22, 1, 21],
+        [1089, 2022, "서부동", "86 - 86", 22, 1, 21],
+        [1090, 2022, "서부동", "87 - 87", 11, 0, 11],
+        [1091, 2022, "서부동", "88 - 88", 9, 2, 7],
+        [1092, 2022, "서부동", "89 - 89", 14, 3, 11],
+        [1093, 2022, "서부동", "90 - 90", 4, 1, 3],
+        [1094, 2022, "서부동", "91 - 91", 6, 1, 5],
+        [1095, 2022, "서부동", "92 - 92", 6, 1, 5],
+        [1096, 2022, "서부동", "94 - 94", 1, 0, 1],
+        [1097, 2022, "서부동", "95 - 95", 1, 0, 1],
+        [1098, 2022, "서부동", "97 - 97", 2, 0, 2],
+        [1099, 2022, "서부동", "100 - 100", 1, 0, 1],
+        [1100, 2022, "서부동", "103 - 103", 1, 0, 1],
+        [1101, 2022, "완산동", "12-12", 1, 0, 1],
+        [1102, 2022, "완산동", "19 - 19", 1, 1, 0],
+        [1103, 2022, "완산동", "20 - 20", 2, 2, 0],
+        [1104, 2022, "완산동", "21 - 21", 4, 2, 2],
+        [1105, 2022, "완산동", "22 - 22", 2, 1, 1],
+        [1106, 2022, "완산동", "23 - 23", 7, 4, 3],
+        [1107, 2022, "완산동", "24 - 24", 6, 1, 5],
+        [1108, 2022, "완산동", "25 - 25", 5, 3, 2],
+        [1109, 2022, "완산동", "26 - 26", 26, 7, 19],
+        [1110, 2022, "완산동", "27 - 27", 19, 13, 6],
+        [1111, 2022, "완산동", "28 - 28", 31, 22, 9],
+        [1112, 2022, "완산동", "29 - 29", 17, 12, 5],
+        [1113, 2022, "완산동", "30 - 30", 25, 13, 12],
+        [1114, 2022, "완산동", "31 - 31", 19, 14, 5],
+        [1115, 2022, "완산동", "32 - 32", 17, 8, 9],
+        [1116, 2022, "완산동", "33 - 33", 18, 15, 3],
+        [1117, 2022, "완산동", "34 - 34", 22, 17, 5],
+        [1118, 2022, "완산동", "35 - 35", 17, 13, 4],
+        [1119, 2022, "완산동", "36 - 36", 21, 15, 6],
+        [1120, 2022, "완산동", "37 - 37", 16, 11, 5],
+        [1121, 2022, "완산동", "38 - 38", 23, 21, 2],
+        [1122, 2022, "완산동", "39 - 39", 18, 9, 9],
+        [1123, 2022, "완산동", "40 - 40", 32, 21, 11],
+        [1124, 2022, "완산동", "41 - 41", 26, 20, 6],
+        [1125, 2022, "완산동", "42 - 42", 21, 18, 3],
+        [1126, 2022, "완산동", "43 - 43", 17, 12, 5],
+        [1127, 2022, "완산동", "44 - 44", 25, 21, 4],
+        [1128, 2022, "완산동", "45 - 45", 26, 18, 8],
+        [1129, 2022, "완산동", "46 - 46", 27, 16, 11],
+        [1130, 2022, "완산동", "47 - 47", 21, 10, 11],
+        [1131, 2022, "완산동", "48 - 48", 29, 16, 13],
+        [1132, 2022, "완산동", "49 - 49", 27, 20, 7],
+        [1133, 2022, "완산동", "50 - 50", 29, 20, 9],
+        [1134, 2022, "완산동", "51 - 51", 34, 22, 12],
+        [1135, 2022, "완산동", "52 - 52", 28, 12, 16],
+        [1136, 2022, "완산동", "53 - 53", 20, 8, 12],
+        [1137, 2022, "완산동", "54 - 54", 33, 22, 11],
+        [1138, 2022, "완산동", "55 - 55", 31, 15, 16],
+        [1139, 2022, "완산동", "56 - 56", 33, 16, 17],
+        [1140, 2022, "완산동", "57 - 57", 34, 18, 16],
+        [1141, 2022, "완산동", "58 - 58", 30, 15, 15],
+        [1142, 2022, "완산동", "59 - 59", 43, 25, 18],
+        [1143, 2022, "완산동", "60 - 60", 34, 18, 16],
+        [1144, 2022, "완산동", "61 - 61", 36, 19, 17],
+        [1145, 2022, "완산동", "62 - 62", 42, 21, 21],
+        [1146, 2022, "완산동", "63 - 63", 35, 9, 26],
+        [1147, 2022, "완산동", "64 - 64", 37, 14, 23],
+        [1148, 2022, "완산동", "65 - 65", 35, 16, 19],
+        [1149, 2022, "완산동", "66 - 66", 37, 15, 22],
+        [1150, 2022, "완산동", "67 - 67", 38, 16, 22],
+        [1151, 2022, "완산동", "68 - 68", 35, 18, 17],
+        [1152, 2022, "완산동", "69 - 69", 29, 8, 21],
+        [1153, 2022, "완산동", "70 - 70", 42, 13, 29],
+        [1154, 2022, "완산동", "71 - 71", 30, 11, 19],
+        [1155, 2022, "완산동", "72 - 72", 21, 3, 18],
+        [1156, 2022, "완산동", "73 - 73", 32, 7, 25],
+        [1157, 2022, "완산동", "74 - 74", 29, 7, 22],
+        [1158, 2022, "완산동", "75 - 75", 17, 4, 13],
+        [1159, 2022, "완산동", "76 - 76", 13, 0, 13],
+        [1160, 2022, "완산동", "77 - 77", 15, 3, 12],
+        [1161, 2022, "완산동", "78 - 78", 13, 3, 10],
+        [1162, 2022, "완산동", "79 - 79", 20, 4, 16],
+        [1163, 2022, "완산동", "80 - 80", 36, 8, 28],
+        [1164, 2022, "완산동", "81 - 81", 16, 4, 12],
+        [1165, 2022, "완산동", "82 - 82", 19, 1, 18],
+        [1166, 2022, "완산동", "83 - 83", 25, 7, 18],
+        [1167, 2022, "완산동", "84 - 84", 14, 4, 10],
+        [1168, 2022, "완산동", "85 - 85", 11, 2, 9],
+        [1169, 2022, "완산동", "86 - 86", 11, 3, 8],
+        [1170, 2022, "완산동", "87 - 87", 11, 4, 7],
+        [1171, 2022, "완산동", "88 - 88", 10, 3, 7],
+        [1172, 2022, "완산동", "89 - 89", 5, 2, 3],
+        [1173, 2022, "완산동", "90 - 90", 4, 1, 3],
+        [1174, 2022, "완산동", "91 - 91", 2, 0, 2],
+        [1175, 2022, "완산동", "92 - 92", 1, 0, 1],
+        [1176, 2022, "완산동", "93 - 93", 4, 1, 3],
+        [1177, 2022, "완산동", "94 - 94", 1, 0, 1],
+        [1178, 2022, "완산동", "97 - 97", 1, 0, 1],
+        [1179, 2022, "남부동", "18 - 18", 3, 2, 1],
+        [1180, 2022, "남부동", "19 - 19", 1, 0, 1],
+        [1181, 2022, "남부동", "20 - 20", 4, 3, 1],
+        [1182, 2022, "남부동", "21 - 21", 3, 3, 0],
+        [1183, 2022, "남부동", "22 - 22", 6, 6, 0],
+        [1184, 2022, "남부동", "23 - 23", 6, 5, 1],
+        [1185, 2022, "남부동", "24 - 24", 12, 5, 7],
+        [1186, 2022, "남부동", "25 - 25", 13, 7, 6],
+        [1187, 2022, "남부동", "26 - 26", 12, 9, 3],
+        [1188, 2022, "남부동", "27 - 27", 21, 18, 3],
+        [1189, 2022, "남부동", "28 - 28", 10, 8, 2],
+        [1190, 2022, "남부동", "29 - 29", 10, 7, 3],
+        [1191, 2022, "남부동", "30 - 30", 22, 16, 6],
+        [1192, 2022, "남부동", "31 - 31", 15, 11, 4],
+        [1193, 2022, "남부동", "32 - 32", 11, 10, 1],
+        [1194, 2022, "남부동", "33 - 33", 15, 11, 4],
+        [1195, 2022, "남부동", "34 - 34", 15, 9, 6],
+        [1196, 2022, "남부동", "35 - 35", 14, 11, 3],
+        [1197, 2022, "남부동", "36 - 36", 11, 8, 3],
+        [1198, 2022, "남부동", "37 - 37", 7, 5, 2],
+        [1199, 2022, "남부동", "38 - 38", 12, 9, 3],
+        [1200, 2022, "남부동", "39 - 39", 14, 12, 2],
+        [1201, 2022, "남부동", "40 - 40", 16, 13, 3],
+        [1202, 2022, "남부동", "41 - 41", 14, 13, 1],
+        [1203, 2022, "남부동", "42 - 42", 10, 7, 3],
+        [1204, 2022, "남부동", "43 - 43", 10, 9, 1],
+        [1205, 2022, "남부동", "44 - 44", 19, 14, 5],
+        [1206, 2022, "남부동", "45 - 45", 12, 11, 1],
+        [1207, 2022, "남부동", "46 - 46", 24, 17, 7],
+        [1208, 2022, "남부동", "47 - 47", 15, 11, 4],
+        [1209, 2022, "남부동", "48 - 48", 27, 22, 5],
+        [1210, 2022, "남부동", "49 - 49", 22, 16, 6],
+        [1211, 2022, "남부동", "50 - 50", 21, 14, 7],
+        [1212, 2022, "남부동", "51 - 51", 27, 19, 8],
+        [1213, 2022, "남부동", "52 - 52", 23, 17, 6],
+        [1214, 2022, "남부동", "53 - 53", 33, 25, 8],
+        [1215, 2022, "남부동", "54 - 54", 46, 30, 16],
+        [1216, 2022, "남부동", "55 - 55", 28, 23, 5],
+        [1217, 2022, "남부동", "56 - 56", 28, 22, 6],
+        [1218, 2022, "남부동", "57 - 57", 24, 16, 8],
+        [1219, 2022, "남부동", "58 - 58", 27, 20, 7],
+        [1220, 2022, "남부동", "59 - 59", 23, 16, 7],
+        [1221, 2022, "남부동", "60 - 60", 40, 30, 10],
+        [1222, 2022, "남부동", "61 - 61", 32, 25, 7],
+        [1223, 2022, "남부동", "62 - 62", 32, 24, 8],
+        [1224, 2022, "남부동", "63 - 63", 49, 29, 20],
+        [1225, 2022, "남부동", "64 - 64", 38, 24, 14],
+        [1226, 2022, "남부동", "65 - 65", 20, 13, 7],
+        [1227, 2022, "남부동", "66 - 66", 26, 17, 9],
+        [1228, 2022, "남부동", "67 - 67", 29, 20, 9],
+        [1229, 2022, "남부동", "68 - 68", 26, 16, 10],
+        [1230, 2022, "남부동", "69 - 69", 20, 8, 12],
+        [1231, 2022, "남부동", "70 - 70", 24, 14, 10],
+        [1232, 2022, "남부동", "71 - 71", 25, 12, 13],
+        [1233, 2022, "남부동", "72 - 72", 20, 4, 16],
+        [1234, 2022, "남부동", "73 - 73", 19, 7, 12],
+        [1235, 2022, "남부동", "74 - 74", 25, 12, 13],
+        [1236, 2022, "남부동", "75 - 75", 15, 5, 10],
+        [1237, 2022, "남부동", "76 - 76", 7, 3, 4],
+        [1238, 2022, "남부동", "77 - 77", 12, 6, 6],
+        [1239, 2022, "남부동", "78 - 78", 15, 5, 10],
+        [1240, 2022, "남부동", "79 - 79", 15, 2, 13],
+        [1241, 2022, "남부동", "80 - 80", 17, 2, 15],
+        [1242, 2022, "남부동", "81 - 81", 19, 2, 17],
+        [1243, 2022, "남부동", "82 - 82", 19, 3, 16],
+        [1244, 2022, "남부동", "83 - 83", 15, 3, 12],
+        [1245, 2022, "남부동", "84 - 84", 15, 4, 11],
+        [1246, 2022, "남부동", "85 - 85", 17, 0, 17],
+        [1247, 2022, "남부동", "86 - 86", 10, 0, 10],
+        [1248, 2022, "남부동", "87 - 87", 14, 4, 10],
+        [1249, 2022, "남부동", "88 - 88", 12, 1, 11],
+        [1250, 2022, "남부동", "89 - 89", 9, 1, 8],
+        [1251, 2022, "남부동", "90 - 90", 6, 2, 4],
+        [1252, 2022, "남부동", "91 - 91", 3, 0, 3],
+        [1253, 2022, "남부동", "92 - 92", 5, 1, 4],
+        [1254, 2022, "남부동", "93 - 93", 7, 1, 6],
+        [1255, 2022, "남부동", "94 - 94", 3, 0, 3],
+        [1256, 2022, "남부동", "95 - 95", 1, 0, 1],
+        [1257, 2022, "남부동", "96 - 96", 2, 0, 2],
+        [1258, 2022, "남부동", "99 - 99", 1, 0, 1],
+        [1259, 2022, "남부동", "100 - 100", 1, 0, 1],
+      ],
+      dummyTable: [
+        { columnNm: "spm_row", dataTy: "INT" },
+        { columnNm: "YEAR", dataTy: "INT" },
+        { columnNm: "EMD", dataTy: "STRING" },
+        { columnNm: "AGE", dataTy: "STRING" },
+        { columnNm: "SM", dataTy: "INT" },
+        { columnNm: "MALE", dataTy: "INT" },
+        { columnNm: "FEMALE", dataTy: "INT" },
+      ],
+
+      jobGroup: {},
+      getDataTable: {},
+      // 차트 데이터로 사용할 리스트
+      xAxisList: [],
+      yAxisList: [],
+      // db에서 가져온 row데이터
+      rowdataList: [],
+
+      // 생성할 차트의 component 이름
+      componentName: null,
+      // 생성할 차트이름
+      chartName: null,
+
+      selectedX: "",
+      selectedY: "",
+      selectedGroup: "",
+      selectedGroupNm: "",
+
+      selectedFieldArr: [],
+      groupArr: [],
+
+      // 다중 데이터 사용 여부 확인
+      isMultiData: false,
+      // 선택된 계산 방법
+      selectedCal: "default",
+
+      // 차트 테마 설정
+      themes: [
+        "Frozen",
+        "Dark",
+        "Dataviz",
+        "Kelly",
+        "Material",
+        "Micro",
+        "Moonrise",
+        "Spirited",
+      ],
+      selectedTheme: "",
+    };
+  },
+  components: {
+    JobContainer,
+    // 차트 컴포넌트
+    ChartCmmn,
+  },
+  methods: {
+    // 테마 선택 함수
+    setThemes: function (e) {
+      this.selectedTheme = e.target.value;
+    },
+    selectJobGroup: function () {
+      let vm = this;
+      axios({
+        url: "/job/selectJobGroup.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: JSON.stringify({ group_id: "JOBGROUP_2024020517352987547982" }),
+      })
+        .then(function (response) {
+          vm.jobGroup = response.data.resultData.jobGroup;
+        })
+        .catch(function (error) {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+    // 데이터 가져오기
+    getResult: function (table) {
+      this.rowdataList = table.rowData;
+      // x축, y축 select option에 추가를 위함
+      for (let i = 0; i < table.columnDatas.length; i++) {
+        let Obj = {
+          index: i,
+          columnNm: table.columnDatas[i].columnNm,
+        };
+        if (
+          table.columnDatas[i].dataTy == "INTEGER" ||
+          table.columnDatas[i].dataTy == "DOUBLE" ||
+          table.columnDatas[i].dataTy == "FLOAT" ||
+          table.columnDatas[i].dataTy == "LONG" ||
+          table.columnDatas[i].dataTy == "NUMBER"
+        ) {
+          this.yAxisList.push(Obj);
+          this.xAxisList.push(Obj);
+        } else {
+          this.xAxisList.push(Obj);
+        }
+      }
+
+      this.rowdataList = this.dummyDataList;
+      // x축, y축 select option에 추가를 위함
+      for (let i = 0; i < this.dummyTable.length; i++) {
+        let Obj = {
+          index: i,
+          columnNm: this.dummyTable[i].columnNm,
+        };
+        if (
+          this.dummyTable[i].dataTy == "INT" ||
+          this.dummyTable[i].dataTy == "DOUBLE" ||
+          this.dummyTable[i].dataTy == "FLOAT" ||
+          this.dummyTable[i].dataTy == "LONG" ||
+          this.dummyTable[i].dataTy == "NUMBER"
+        ) {
+          this.yAxisList.push(Obj);
+          this.xAxisList.push(Obj);
+        } else {
+          this.xAxisList.push(Obj);
+        }
+      }
+    },
+
+    // X축 option 선택
+    selectedXAxis: function (e) {
+      this.selectedX = e.target.value;
+      // 선택된 x축을 제외한 나머지 x축을 groupArr에 저장
+      this.groupArr = this.xAxisList.filter(
+        (item) => item.index != this.selectedX
+      );
+      this.createChartData();
+    },
+    // Y축 option 선택
+    selectedYAxis: function (e) {
+      this.selectedY = e.target.value;
+      const exists = this.selectedFieldArr.some(
+        (item) => item.valIndex == this.selectedY
+      );
+
+      // 선택된 y축이 이미 선택된 필드에 있는 경우 return
+      if (exists) {
+        return;
+      }
+
+      // 다중 데이터 사용이 아닌 경우, 선택된 필드 초기화
+      if (!this.isMultiData) {
+        this.selectedFieldArr = [];
+      }
+
+      // COL_AND_LINE은 데이터가 2개가 필요하여, 선택된 데이터가 2개 이상 적용되지 않게 설정
+      if (this.chartName == "mix_chart" && this.selectedFieldArr.length > 1) {
+        alert("선택된 필드는 2개까지만 가능합니다.");
+        return;
+      }
+      let data = {
+        valIndex: Number(this.selectedY),
+        valNm: e.target.options[e.target.selectedIndex].text,
+      };
+      this.selectedFieldArr.push(data);
+      this.createChartData();
+    },
+    changeSelectGroup: function (e) {
+      this.selectedGroup = e.target.value;
+      this.selectedGroupNm = e.target.options[e.target.selectedIndex].text;
+      this.createChartData();
+    },
+    // 차트 데이터 생성
+    createChartData: function () {
+      let vm = this;
+      if (vm.selectedX === "" || vm.selectedY === "") {
+        return;
+      }
+      // 초기화
+      vm.datalist = [];
+      vm.datalist = chartDataTransform.createData(
+        vm.dummyDataList,
+        vm.selectedX,
+        vm.selectedFieldArr,
+        vm.selectedGroup
+      );
+    },
+    // chart 변경 시 실행
+    changeChartName: function (event, chartType) {
+      this.componentName = event.target.name;
+      this.chartName = chartType;
+
+      // 초기화
+      this.selectedX = "";
+      this.selectedY = "";
+      this.selectedGroup = "";
+      this.selectedFieldArr = [];
+      this.groupArr = [];
+
+      // 차트 종류에 따라 다중 데이터 사용 여부 체크 하여 isMultiData에 저장
+      if (
+        this.chartName === "column_chart_v" ||
+        this.chartName === "column_chart_h" || // || this.chartName === "line_chart"
+        this.chartName === "node_line_chart" ||
+        this.chartName === "liin_chart_back" ||
+        this.chartName === "pie_chart" ||
+        this.chartName === "dounet_chart" ||
+        this.chartName === "semicircle_chart"
+      ) {
+        this.isMultiData = false;
+      } else {
+        this.isMultiData = true;
+      }
+    },
+    // y축 배열 내 선택된 필드 삭제
+    deleteField: function (index) {
+      this.selectedFieldArr.splice(index, 1);
+    },
+  },
+  watch: {
+    selectedFieldArr: function () {
+      this.createChartData();
+    },
+  },
+  mounted() {
+    this.jobGroup = this.$getDefaultJobGroup().jobGroup;
+  },
+};
+</script>
+
+<style scoped>
+#chartdiv {
+  width: 100%;
+  height: 500px;
+}
+button {
+  margin: 10px;
+  background-color: antiquewhite;
+  border-radius: 10px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/chart/ColAndLine.vue (added)
+++ client/views/component/chart/ColAndLine.vue
@@ -0,0 +1,279 @@
+<template>
+  <div
+    class="chartdiv"
+    id="chartdiv"
+    ref="chartdiv"
+    style="width: 100%; height: 700px"
+  ></div>
+</template>
+
+<script>
+import * as am5 from "@amcharts/amcharts5";
+import * as am5xy from "@amcharts/amcharts5/xy";
+import * as am5percent from "@amcharts/amcharts5/percent";
+import am5themes_Animated from "@amcharts/amcharts5/themes/Animated";
+import chartDataTransform from "./chartDataTransform";
+
+export default {
+  props: {
+    datalist: {
+      type: Array,
+      default: [],
+    },
+    chartName: {
+      type: String,
+    },
+    selectedFieldArr: {
+      type: Array,
+      default: [],
+    },
+    selectedCal: {
+      type: String,
+      default: "default",
+    },
+  },
+  data() {
+    return {
+      jsonData: [],
+      chartType: null,
+    };
+  },
+  components: {},
+  methods: {
+    // 그룹 선택으로 인한 데이터 필터링 함수
+    changeSelectSubGroup: function (event) {
+      this.selectedSubGroup = event.target.value;
+      // 필터 적용 함수 호출
+      const filterDataList = chartDataTransform.setFilteringData(
+        this.datalist,
+        this.subGroupArr,
+        this.selectedSubGroup
+      );
+      this.transformData(filterDataList);
+    },
+    changeSubGroupData: function (val) {
+      if (Array.isArray(val)) {
+        this.subGroupArr = [];
+        this.subGroupArr = val
+          .map((data) => data.group)
+          .filter((value, index, self) => self.indexOf(value) === index)
+          .map((column, index) => {
+            return {
+              index: index,
+              columnNm: column,
+            };
+          });
+        // subGroupArr sort 처리
+        this.subGroupArr.sort((a, b) =>
+          a.columnNm < b.columnNm ? -1 : a.columnNm > b.columnNm ? 1 : 0
+        );
+      }
+    },
+    //y 객체 배열 생성
+    createYData: function () {
+      this.selectedFieldArr = this.createDataObj.valueAxis.map((field) => {
+        return {
+          valNm: field.columnNm,
+          valIndex: field.columnIdx,
+        };
+      });
+    },
+    // 데이터 생성 함수
+    // dataList를 받아와 차트에 표현할 데이터로 가공
+    transformData: function (val) {
+      if (this.selectedCal === "default") {
+        this.chartJsondData = val;
+        this.createChart();
+        return;
+      }
+      // 전체 데이터를 그룹화하여 가공 (All Data)
+      this.groupsResult = chartDataTransform.dataGrouping(
+        val,
+        this.selectedFieldArr
+      );
+      if (!this.isSelectGroup) {
+        // groupSubArr에 group의 key를 중복을 제거하고 담음
+        this.changeSubGroupData(this.datalist);
+      }
+      // }
+      // 데이터 가공 함수
+      this.chartJsondData = chartDataTransform.calculateSetting(
+        this.groupsResult,
+        this.selectedCal
+      );
+
+      this.createChart();
+    },
+    initializeChartArea: function () {
+      let chartWarp = this.$refs["chartdiv"]; // 차트 상위 div ref 매칭
+      chartWarp.innerHTML = ""; // 차트 상위 div 내용 초기화 (기존 차트 삭제)
+
+      let div = document.createElement("div"); // 차트를 담을 빈 div 생성 (차트 하위 div)
+      div.style.width = "100%"; // 차트를 담을 div의 넓이
+      div.style.height = "100%"; // 차트를 담을 div의 높이
+      chartWarp.appendChild(div); // 차트 상위 div 안에 차트 하위 div를 추가
+
+      let chartArea = am5.Root.new(chartWarp.firstChild);
+
+      chartArea.setThemes([am5themes_Animated.new(root)]);
+
+      return chartArea;
+    },
+    createChart: function () {
+      let vm = this;
+      let data = vm.chartJsondData;
+
+      let income = Object.keys(data[0])[1];
+      let expenses = Object.keys(data[0])[2];
+
+      let root = vm.initializeChartArea(); // 차트 초기화
+
+      let chart = root.container.children.push(
+        am5xy.XYChart.new(root, {
+          panX: false,
+          panY: false,
+          wheelX: "panX",
+          wheelY: "zoomX",
+          paddingLeft: 0,
+          layout: root.verticalLayout,
+        })
+      );
+
+      chart.set(
+        "scrollbarX",
+        am5.Scrollbar.new(root, {
+          orientation: "horizontal",
+        })
+      );
+
+      let xRenderer = am5xy.AxisRendererX.new(root, {
+        minorGridEnabled: true,
+        minGridDistance: 60,
+      });
+      let xAxis = chart.xAxes.push(
+        am5xy.CategoryAxis.new(root, {
+          categoryField: "categoryData",
+          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,
+          }),
+        })
+      );
+
+      let series1 = chart.series.push(
+        am5xy.ColumnSeries.new(root, {
+          name: income,
+          xAxis: xAxis,
+          yAxis: yAxis,
+          valueYField: income,
+          categoryXField: "categoryData",
+          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: "categoryData",
+          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, {}));
+
+      let legend = chart.children.push(
+        am5.Legend.new(root, {
+          centerX: am5.p50,
+          x: am5.p50,
+        })
+      );
+      legend.data.setAll(chart.series.values);
+
+      series1.appear(1000);
+      chart.appear(1000, 300);
+
+      root._logo.dispose(); //amChart 로고 삭제
+    },
+  },
+  watch: {
+    datalist: {
+      handler: function (newVal, oldVal) {
+        this.transformData(newVal);
+      },
+      deep: true,
+    },
+    selectedCal: {
+      handler: function () {
+        this.transformData(this.datalist);
+      },
+    },
+  },
+  mounted() {
+    this.datalist = this.createDataObj.data_list;
+    this.chartName = this.createDataObj.chart_knd;
+    this.isMultiData = this.createDataObj.multidata_use_yn;
+    this.selectedCal = this.createDataObj.chart_cal;
+
+    this.transformData(this.datalist);
+  },
+};
+</script>
+
+<style scoped>
+#chartdiv {
+  width: 100%;
+  height: 500px;
+}
+button {
+  margin: 10px;
+  background-color: antiquewhite;
+  border-radius: 10px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/chart/ColumnLablesChart.vue (added)
+++ client/views/component/chart/ColumnLablesChart.vue
@@ -0,0 +1,202 @@
+<template>
+  <div
+    class="chartdiv"
+    id="chartdiv"
+    ref="chartdiv"
+    style="width: 100%; height: 700px"
+  ></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 {
+  props: {
+    datalist: {
+      type: Array,
+      default: [],
+    },
+    chartName: {
+      type: String,
+    },
+  },
+  data() {
+    return {
+      jsonData: [],
+      root: null,
+    };
+  },
+  components: {},
+  methods: {
+    initializeChartArea: function () {
+      let chartWarp = this.$refs["chartdiv"]; // 차트 상위 div ref 매칭
+      chartWarp.innerHTML = ""; // 차트 상위 div 내용 초기화 (기존 차트 삭제)
+
+      let div = document.createElement("div"); // 차트를 담을 빈 div 생성 (차트 하위 div)
+      div.style.width = "100%"; // 차트를 담을 div의 넓이
+      div.style.height = "100%"; // 차트를 담을 div의 높이
+      chartWarp.appendChild(div); // 차트 상위 div 안에 차트 하위 div를 추가
+
+      let chartArea = am5.Root.new(chartWarp.firstChild);
+
+      chartArea.setThemes([am5themes_Animated.new(root)]);
+
+      return chartArea;
+    },
+    createChart: function (data) {
+      let vm = this;
+
+      let root = this.initializeChartArea(); // 차트 초기화
+
+      let series = null;
+      let chart = null;
+
+      // 동적 데이터 설정
+      let categoryAxisRenderer = null;
+      let valueAxisRenderer = null;
+      let categoryAxes = null;
+      let valueAxes = null;
+      let categoryInversed = null; // 카테고리 셀 순서 (순차: false, 역순: true)
+
+      categoryAxisRenderer =
+        vm.chartName == "column_chart_v" ? "AxisRendererX" : "AxisRendererY";
+      valueAxisRenderer =
+        vm.chartName == "column_chart_v" ? "AxisRendererY" : "AxisRendererX";
+      categoryAxes = vm.chartName == "column_chart_v" ? "xAxes" : "yAxes";
+      valueAxes = vm.chartName == "column_chart_v" ? "yAxes" : "xAxes";
+      categoryInversed = vm.chartName == "column_chart_v" ? false : true;
+
+      // Create chart
+      let panX = true;
+      let panY = true;
+      let wheelX = "panX";
+      let wheelY = vm.chartName == "column_chart_v" ? "panY" : "zoomX";
+
+      chart = root.container.children.push(
+        am5xy.XYChart.new(root, {
+          panX: panX,
+          panY: panY,
+          wheelX: wheelX,
+          wheelY: wheelY,
+        })
+      );
+
+      let categoryRenderer = am5xy[categoryAxisRenderer].new(root, {
+        minGridDistance: 30,
+        minorGridEnabled: true,
+        inversed: categoryInversed,
+      });
+
+      categoryRenderer.labels.template.setAll({
+        rotation: -90,
+        centerY: am5.p50,
+        centerX: am5.p100,
+        paddingRight: 15,
+      });
+
+      categoryRenderer.grid.template.setAll({
+        stroke: am5.color(0xf15f5f),
+        location: 1,
+      });
+
+      let categoryAxis = chart[categoryAxes].push(
+        am5xy.CategoryAxis.new(root, {
+          maxDeviation: 0.3,
+          categoryField: "categoryData",
+          renderer: categoryRenderer,
+          tooltip: am5.Tooltip.new(root, {}),
+        })
+      );
+
+      let valueRenderer = am5xy[valueAxisRenderer].new(root, {
+        strokeOpacity: 0.1,
+      });
+
+      let valueAxis = chart[valueAxes].push(
+        am5xy.ValueAxis.new(root, {
+          maxDeviation: 0.3,
+          renderer: valueRenderer,
+        })
+      );
+
+      let xAxis = null;
+      let yAxis = null;
+      let categoryField = null;
+      let valueField = null;
+      let categoryXY = null;
+      let valueXY = null;
+      if (vm.chartName == "column_chart_v") {
+        xAxis = categoryAxis;
+        yAxis = valueAxis;
+        categoryField = "categoryXField";
+        valueField = "valueYField";
+        categoryXY = "categoryX";
+        valueXY = "valueY";
+      } else {
+        xAxis = valueAxis;
+        yAxis = categoryAxis;
+        categoryField = "categoryYField";
+        valueField = "valueXField";
+        categoryXY = "categoryY";
+        valueXY = "valueX";
+      }
+
+      series = chart.series.push(
+        am5xy.ColumnSeries.new(root, {
+          name: "Series 1",
+          xAxis: xAxis,
+          yAxis: yAxis,
+          [valueField]: "value",
+          [categoryField]: "categoryData",
+          tooltip: am5.Tooltip.new(root, {
+            labelText: "{valueY}",
+          }),
+        })
+      );
+
+      series.columns.template.setAll({
+        cornerRadiusTL: 5,
+        cornerRadiusTR: 5,
+        strokeOpacity: 0,
+      });
+      series.columns.template.adapters.add("fill", function (fill, target) {
+        return chart.get("colors").getIndex(series.columns.indexOf(target));
+      });
+
+      series.columns.template.adapters.add("stroke", function (stroke, target) {
+        return chart.get("colors").getIndex(series.columns.indexOf(target));
+      });
+
+      categoryAxis.data.setAll(data);
+      series.data.setAll(data);
+      series.appear(1000);
+    },
+  },
+  watch: {
+    datalist: {
+      handler: function (newVal, oldVal) {
+        this.jsonData = newVal;
+        this.createChart(this.jsonData);
+      },
+      deep: true,
+    },
+  },
+  mounted() {
+    this.jsonData = this.datalist;
+  },
+};
+</script>
+
+<style scoped>
+#chartdiv {
+  width: 100%;
+  height: 500px;
+}
+button {
+  margin: 10px;
+  background-color: antiquewhite;
+  border-radius: 10px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/chart/chartDataTransform.js (added)
+++ client/views/component/chart/chartDataTransform.js
@@ -0,0 +1,136 @@
+
+const chartDataTransform = {
+    /**
+     * rowdata를 사용자 선택에 맞게 반환
+     * @param rowdata : 원본 데이터
+     * @param x : x축 객체 데이터
+     * @param yArr : y축 배열 데이터
+     */
+    createData: function(rowdata, xAxisList, yAxisList, group){
+        let resultArr = [];
+        let x;
+        //x축 객체 배열 데이터를 가져와서 columnNm 값을 가져옴
+        if(xAxisList.length !== 0){
+            x = xAxisList[0].columnIdx;
+        }
+
+        //y축 객체 배열 데이터를 가져와서 columnNm 값을 배열로 가져옴
+        let yArr = yAxisList.map(field => {
+            return {
+                valNm: field.columnNm,
+                valIndex: field.columnIdx
+            }
+        });
+
+        if (yArr.length > 0) {
+            // 반복문을 통해 y축의 데이터를 가져와서 datalist에 추가
+            for(let i = 0; i < rowdata.length; i++) {
+                let data = {
+                    categoryData: rowdata[i][x]
+                }
+                // data에 y축의 다중데이터 추가
+                yArr.forEach(field => {
+                    const fieldName = field.valNm;
+                    if(!data[fieldName]) {
+                        data[fieldName] = 0;
+                    }
+                    data[fieldName] += Number(rowdata[i][field.valIndex]);
+                });
+                // group 추가
+                if(group !== undefined && group !== ""){
+                data.group = rowdata[i][group];}
+                resultArr.push(data);
+            }
+        }
+        return resultArr;
+
+    },
+    /**
+     * 변환된 데이터 그룹화
+     * @param data : 변환된 데이터
+     * @param yArr : y축 카테고리 배열 데이터
+     */
+    dataGrouping: function(data, yArr){
+        if (!Array.isArray(data) || !data) {
+            return {};
+        }
+        return data.reduce((acc, cur) => {
+            if (!acc[cur.categoryData]) {
+                acc[cur.categoryData] = {};
+                // 각 필드 초기화
+                yArr.forEach(field => {
+                    acc[cur.categoryData][field.valNm] = { values: [], sum: 0, min: Infinity, max: -Infinity, count: 0 };
+                });
+            }
+
+            // 각 필드별 합계 계산
+            yArr.forEach(field => {
+                const fieldName = field.valNm;
+                // cur[fieldName]이 배열인지 확인하고, 아니라면 배열로 만듦
+                let values = Array.isArray(cur[fieldName]) ? cur[fieldName] : [cur[fieldName]];
+                values.forEach(value => {
+                    acc[cur.categoryData][fieldName].values.push(value);
+                    acc[cur.categoryData][fieldName].sum += value;
+                    acc[cur.categoryData][fieldName].min = Math.min(acc[cur.categoryData][fieldName].min, value);
+                    acc[cur.categoryData][fieldName].max = Math.max(acc[cur.categoryData][fieldName].max, value);
+                    acc[cur.categoryData][fieldName].count += 1;
+                });
+            });
+
+            return acc;
+        }, {});
+    },
+
+    /**
+     * 데이터 계산 값으로 가공 (min, max, sum, avg)
+     * @param groupData : 그룹화된 데이터
+     * @param selectedCal : 사용자가 선택한 계산 값
+     */
+    calculateSetting: function(groupData, selectedCal) {
+        let result = Object.keys(groupData).map(categoryData => {
+            const result = { categoryData: categoryData };
+            Object.keys(groupData[categoryData]).forEach(fieldName => {
+                const fieldData = groupData[categoryData][fieldName];
+                switch (selectedCal) {
+                    case "sum":
+                        result[fieldName] = fieldData.sum;
+                        break;
+                    case "avg":
+                        result[fieldName] = fieldData.sum / fieldData.count;
+                        break;
+                    case "min":
+                        result[fieldName] = fieldData.min;
+                        break;
+                    case "max":
+                        result[fieldName] = fieldData.max;
+                        break;
+                }
+            });
+            return result;
+        });
+        return result;
+    },
+
+    /**
+     * 데이터 필터링
+     * 선택 된 그룹의 데이터로 가공
+     */
+    setFilteringData : function(dataList ,subGroupArr, selectedSubGroup){
+        // groupSubArr에서 selectedSubGroup의 index를 찾아서 그 columnNm을 찾아옴
+        const selectedSubGroupNm = subGroupArr.find(column => column.index === Number(selectedSubGroup)).columnNm;
+        return dataList.filter(data => data.group === selectedSubGroupNm);
+    },
+    /**
+     * 음수 차트 데이터 처리
+     */
+    convertValuesToNegative: function(data, fieldNm){
+        // 데이터 리스트를 순회하며 각 객체의 값을 확인
+        let test = data.forEach(obj => {
+            obj[fieldNm] = -Math.abs(obj[fieldNm]);
+        });
+        return test;
+    },
+    
+}
+
+export default chartDataTransform;(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/common/AlertModal.vue (added)
+++ client/views/component/common/AlertModal.vue
@@ -0,0 +1,151 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container small-modal">
+      <div class="modal-title text-ct">
+        <h2>{{ title }}</h2>
+      </div>
+      <div class="modal-content-monthly">
+        <p class="alert-write text-ct" v-html="message">
+        </p>
+        <div v-if="radioAt">
+          <p>원본파일 : </p>
+          <label></label>
+          <p>동작 : </p>
+          <label><input type="radio" value="move" v-model="moveOrCopy.type">덮어쓰기</label>
+          <label><input type="radio" value="cancel" v-model="moveOrCopy.type">건너뛰기</label>
+          <label><input type="checkbox" v-model="moveOrCopy.checkBox">항상 이 동작 사용</label>
+        </div>
+      </div>
+      <div class="modal-end flex justify-center" style="flex-wrap: nowrap;">
+        <button class="blue-btn large-btn" id="confirmOk" @click="closeModal" @keyup.enter="closeModal">확인</button>
+        <button class="gray-btn large-btn" id="confirmCancel" @click="closeModal" v-show="confirmAt">취소</button>
+        <button class="gray-btn large-btn" id="confirmRadioCancel" @click="closeModal" v-show="radioAt">취소</button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+
+export default {
+  props: {
+    title: {
+      type: String,
+      default: '모달 제목'
+    },
+    message: {
+      type: String,
+      default: '경고 메세지를 입력해주세요. <br /> 삭제,수정,추가 등등'
+    },
+  },
+  data() {
+    return {
+      isModalOpen: false,
+      activeTab: 'tab1',
+      modalType: 'tab-modal',
+      title: this.title,
+      message: this.message,
+      confirmAt: false,
+      radioAt: false,
+      moveOrCopy: {
+        type: null,
+        checkBox: null
+      },
+    }
+  },
+  methods: {
+    // 탭 변경
+    showTab: function (tabName) {
+      this.activeTab = tabName;
+    },
+
+    // 닫기
+    closeModal: function () {
+      this.isModalOpen = false;
+      this.confirmAt = false;
+      this.radioAt = false;
+    },
+
+    // 모달 호출
+    showModal: function () {
+
+      this.isModalOpen = true;
+      document.getElementById("confirmOk").focus()
+    },
+
+    // confirm 호출
+    showConfirm: async function () {
+      this.confirmAt = true;
+      this.isModalOpen = true;
+      document.getElementById("confirmOk").focus()
+      const promise = new Promise((resolve, reject) => {
+        document.getElementById("confirmCancel").addEventListener("click", async () => {
+          resolve('cancel')
+        });
+
+        document.getElementById("confirmOk").addEventListener("click", async () => {
+          resolve('ok')
+        });
+      });
+
+      return promise.then(
+        (id) => {
+          if (id == 'cancel') {
+            return false;
+          } else if (id == 'ok') {
+            return true;
+          }
+        }
+      );
+    },
+
+    // radio confirm 호출
+    showRadioConfirm: async function () {
+      this.radioAt = true;
+      this.isModalOpen = true;
+      this.moveOrCopy.type = "move";
+      this.moveOrCopy.checkBox = false;
+
+      document.getElementById("confirmOk").focus()
+      const promise = new Promise((resolve, reject) => {
+        document.getElementById("confirmRadioCancel").addEventListener("click", async () => {
+          resolve('cancel')
+        });
+
+        document.getElementById("confirmOk").addEventListener("click", async () => {
+          resolve('ok')
+        });
+      });
+
+      return promise.then(
+        (id) => {
+          if (id == 'cancel') {
+            this.moveOrCopy.type = "cancel"
+            this.moveOrCopy.checkBox = false
+            return this.moveOrCopy;
+          } else if (id == 'ok') {
+            return this.moveOrCopy;
+          }
+        }
+      );
+    },
+
+    setTitle: function (Title) {
+      this.title = Title;
+    },
+
+    setMessage: function (message) {
+      this.message = message;
+    },
+
+  },
+  watch: {
+
+  },
+  computed: {
+
+  },
+  components: {
+
+  },
+}
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/common/Component_CodeList.vue (added)
+++ client/views/component/common/Component_CodeList.vue
@@ -0,0 +1,106 @@
+<template>
+  <div class="content-box overflow-y">
+    <div class="content-box">
+      <ul class="content-list" v-if="codeList.length > 0">
+        <li class="all-data-search cursor">
+          <a class="file-list" @click="allSelect">
+            <div class="flex align-center">
+              <p class="mr5">
+                <img src="../../../resources/img/icon/iconCategory.png" />
+              </p>
+              <p>전체</p>
+            </div>
+          </a>
+        </li>
+        <li class="cursor" v-for="(item, idx) in codeList" :key="idx">
+          <a :class="{ 'file-list': true, selected: selected === idx }" @click="categorySelect(item, idx)">
+            <div class="flex align-center">
+              <p class="mr5">
+                <svg-icon type="mdi" :path="path" :color="'#fbbe28'"></svg-icon>
+              </p>
+              <p>{{ item.codeNm }}</p>
+            </div>
+          </a>
+        </li>
+      </ul>
+      <ul class="content-list" v-else></ul>
+    </div>
+  </div>
+</template>
+<script>
+import axios from "axios";
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiFolder } from "@mdi/js";
+
+export default {
+  props: {
+    groupCode: {
+      type: String,
+      default: "DATA_CTGRY",
+    },
+    categoryIdx: {
+      type: Number,
+    },
+  },
+  data() {
+    return {
+      codeList: [],
+      groupCode: this.groupCode,
+      path: mdiFolder,
+      selected: null,
+    };
+  },
+  methods: {
+    categorySelect: function (item, idx) {
+      this.selected = idx;
+      this.$emit("selectCode", item.cmmnCode);
+    },
+    allSelect: function () {
+      this.selected = null;
+      this.$emit("selectCode", null);
+    },
+    // 초기화
+    init: function () {
+      const vm = this;
+      axios({
+        url: "/common/getCodeList.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: JSON.stringify({ groupCode: vm.groupCode }),
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.status == 200) {
+            vm.codeList = response.data.resultData.codeList;
+          } else {
+            this.$showAlert("알람", response.data.checkMessage.message);
+          }
+        })
+        .catch(function (error) {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+  },
+  watch: {
+    categoryIdx: function (val) {
+      this.selected = val;
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+  },
+  mounted() {
+    this.init();
+  },
+};
+</script>
+<style scoped>
+.all-data-search {
+  font-size: 15px;
+  font-weight: bold;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/common/Component_Dataset.vue (added)
+++ client/views/component/common/Component_Dataset.vue
@@ -0,0 +1,61 @@
+<template>
+  <div class="content-box overflow-y">
+    <div class="content-box">
+      <ul class="content-list" v-if="postList.length > 0">
+        <li class="cursor" v-for="(item, idx) in postList" :key="idx">
+          <a
+            @click="categorySelect(item, idx)"
+            :class="{ 'file-list': true, selected: selected === idx }"
+          >
+            <div class="flex align-center">
+              <p class="mr5">
+                <svg-icon type="mdi" :path="path" :color="'#fbbe28'"></svg-icon>
+              </p>
+              <p>{{ item.post_sj }}</p>
+            </div>
+          </a>
+        </li>
+      </ul>
+      <ul class="content-list" v-else></ul>
+    </div>
+  </div>
+</template>
+
+<script>
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiFolder } from "@mdi/js";
+
+export default {
+  props: {
+    dataPostList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      postList: this.dataPostList,
+      groupCode: this.groupCode,
+      path: mdiFolder,
+      selected: 0,
+    };
+  },
+  methods: {
+    categorySelect: function (item, idx) {
+      this.selected = idx;
+      this.$emit("selectDatasetPost", item);
+    },
+  },
+  watch: {
+    dataPostList: function (newVal, oldVal) {
+      this.postList = newVal;
+      if (this.postList.length > 0) {
+        this.categorySelect(this.postList[0], 0);
+      }
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/EhojoConnection.vue (added)
+++ client/views/component/connection/EhojoConnection.vue
@@ -0,0 +1,382 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container large-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>차세대 API 연결</h2>
+          <button class="close-btn" @click="$emit('fnCloseModal')">
+            <svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="modal-content-monthly">
+        <div v-if="modalView == 'default'">
+          <div class="content-titleZone">
+            <p class="box-title">기본 정보</p>
+          </div>
+          <div>
+            <table class="form-table">
+              <colgroup>
+                <col style="width: 15%" />
+                <col style="width: 35%" />
+                <col style="width: 15%" />
+                <col style="width: 35%" />
+              </colgroup>
+              <tbody>
+                <tr>
+                  <th>송신기관 코드</th>
+                  <td><input type="text" v-model="job.lafCode" class="full-input" placeholder="송신기관 코드를 입력해 주세요."></td>
+                  <th>업무구분 코드</th>
+                  <td><input type="text" v-model="job.taskCode" class="full-input" placeholder="업무구분 코드를 입력해 주세요."></td>
+                </tr>
+                <tr>
+                  <th>테이블 명</th>
+                  <td><input type="text" v-model="job.tableName" class="full-input" placeholder="테이블 명을 입력해 주세요."></td>
+                  <th>인터페이스 ID</th>
+                  <td><input type="text" v-model="job.ifId" class="full-input" placeholder="인터페이스 ID를 입력해 주세요."></td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+          <div class="flex justify-between">
+            <div class="flex50 pl0">
+              <div class="content-titleZone flex justify-between align-center mt20">
+                <p class="box-title">헤더 정보</p>
+                <div>
+                  <button class="blue-border-btn small-btn" @click="fnAddRow('header')">헤더 정보 추가</button>
+                </div>
+              </div>
+              <div style="height: 300px; overflow: auto">
+                <table class="list-table" style="table-layout: fixed">
+                  <colgroup>
+                    <col width="10%" />
+                    <col width="30%" />
+                    <col width="50%" />
+                    <col width="10%" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th style="text-align: center">No</th>
+                      <th style="text-align: center">키</th>
+                      <th style="text-align: center">값</th>
+                      <th style="text-align: center">비고</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr v-for="(item, idx) in job.headers" :key="idx">
+                      <td style="text-align: center">{{ idx + 1 }}</td>
+                      <td><input type="text" class="full-input" v-model.trim="item.key" /></td>
+                      <td><input type="text" class="full-input" v-model.trim="item.value" /></td>
+                      <td>
+                        <button v-if="!item.required" class="cbtnDelete" @click="fnDelRow('header', item)">
+                          <img src="../../../resources/img/delete.png" alt="삭제" />
+                        </button>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </div>
+            <div class="flex50 pr0">
+              <div class="content-titleZone flex justify-between align-center mt20">
+                <p class="box-title">바디 정보</p>
+                <div>
+                  <button class="blue-border-btn small-btn" @click="fnAddRow('body')">바디 정보 추가</button>
+                </div>
+              </div>
+              <div style="height: 300px; overflow: auto">
+                <table class="list-table" style="table-layout: fixed">
+                  <colgroup>
+                    <col width="10%" />
+                    <col width="30%" />
+                    <col width="50%" />
+                    <col width="10%" />
+                  </colgroup>
+                  <thead>
+                    <tr>
+                      <th style="text-align: center">No</th>
+                      <th style="text-align: center">키</th>
+                      <th style="text-align: center">값</th>
+                      <th style="text-align: center">비고</th>
+                    </tr>
+                  </thead>
+                  <tbody>
+                    <tr v-for="(item, idx) in job.bodys" :key="idx">
+                      <td style="text-align: center">{{ idx + 1 }}</td>
+                      <td><input type="text" class="full-input" v-model.trim="item.key" /></td>
+                      <td><input type="text" class="full-input" v-model.trim="item.value" /></td>
+                      <td>
+                        <button class="cbtnDelete" @click="fnDelRow('body', item)">
+                          <img src="../../../resources/img/delete.png" alt="삭제" />
+                        </button>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div v-if="modalView == 'preview'">
+          <div class="content-titleZone flex justify-between align-center mt20">
+            <p class="box-title">데이터셋 <small>(총 ROW: {{ rowData.length > 1 ? rowData.length : 0 }}개)</small>
+            </p>
+          </div>
+          <div style="height: 400px; overflow: auto">
+            <table class="list-table table-flow">
+              <template v-if="!$isEmpty(dataTable)">
+                <thead>
+                  <tr>
+                    <th>No</th>
+                    <th v-for="(rowKey, index) of rowKeys" :key="index">{{ rowKey }}</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr v-for="(items, indexI) of rowData" :key="indexI">
+                    <td>{{ indexI + 1 }}</td>
+                    <td v-for="(item, indexJ) of items" :key="indexJ">{{ item }}</td>
+                  </tr>
+                </tbody>
+              </template>
+              <tr v-else>
+                <td>등록된 데이터가 없습니다.</td>
+              </tr>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="modal-end" style="text-align: right">
+        <button type="button" :class="{
+          'small-btn': true,
+          'blue-btn': modalView == 'default',
+          'blue-border-btn': modalView == 'preview',
+        }" @click="fnPrevNextPage">
+          <span v-if="modalView == 'preview'">이전</span>
+          <span v-if="modalView == 'default'">다음</span>
+        </button>
+        <button type="button" v-if="modalView == 'preview'" class="blue-btn small-btn" @click="fnSave">저장</button>
+        <button type="button" class="blue-border-btn small-btn" @click="$emit('fnCloseModal')">취소</button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+// IMPORTS
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiClose } from "@mdi/js";
+import axios from "axios";
+
+export default {
+  components: {
+    SvgIcon: SvgIcon,
+  },
+
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Boolean,
+      default: null,
+    },
+  },
+
+  data() {
+    return {
+      // ICON
+      closePath: mdiClose,
+
+      // DEFAULT
+      isModalOpen: this.openPopup,
+      modalView: "default",
+
+      dataTable: {},
+      rowData: [],
+      rowKeys: [],
+
+      job: Object.assign({}, this.$getDefaultJobGroup().connectionEhojo),
+
+      // 기본값
+      defaultHeaders: [
+        { index: 1, key: "ifId", value: null, required: true },
+        { index: 2, key: "tranId", value: null, required: true },
+        { index: 3, key: "trnmtInstCd", value: null, required: true },
+        { index: 4, key: "rcptnInstCd", value: null, required: true },
+        { index: 5, key: "trnmtInstSysCd", value: null, required: true },
+        { index: 6, key: "rcptnInstSysCd", value: null, required: true },
+        { index: 7, key: "transfGramNo", value: null, required: false },
+        { index: 8, key: "userDeptCode", value: null, required: false },
+        { index: 9, key: "userName", value: null, required: false },
+      ],
+      defaultBodys: [
+        { index: 1, key: "curPage", value: 1, required: false },
+        { index: 2, key: "pageSize", value: 3000, required: false },
+      ],
+    };
+  },
+
+  mounted() {
+    this.init();
+  },
+
+  methods: {
+    // 초기화 function
+    init() {
+      this.modalView = "default";
+
+      if (this.jobItem.itm_id != null) {
+        this.job = this.jobItem.itm;
+        this.dataTable = this.jobItem.dataTable;
+        this.rowData = this.dataTable.rowData;
+        for (let columnData of this.dataTable.columnDatas) {
+          this.rowKeys.push(columnData.columnNm);
+          // displayColumnNm 초기값 설정
+          columnData.displyColumnNm = columnData.orginlColumnNm;
+        }
+      } else {
+        this.dataTable = {};
+        this.rowData = [];
+        this.rowKeys = [];
+
+        // 헤더, 바디 기본값 설정
+        this.job.headers = this.defaultHeaders;
+        this.job.bodys = this.defaultBodys;
+      }
+    },
+
+    // ##파라미터
+    // 행 추가
+    fnAddRow(type) {
+      if (type == 'header') {
+        this.job.headers.push({
+          index: this.job.headers.length + 1,
+          key: null,
+          value: null,
+          required: false
+        });
+      } else if (type == 'body') {
+        this.job.bodys.push({
+          index: this.job.bodys.length + 1,
+          key: null,
+          value: null,
+          required: false
+        });
+      }
+    },
+    // 행 삭제
+    fnDelRow(type, item) {
+      if (type == 'header') {
+        this.job.headers = this.job.headers.filter(parameter => parameter !== item);
+      } else if (type == 'body') {
+        this.job.bodys = this.job.bodys.filter(parameter => parameter !== item);
+      }
+    },
+
+    // 유효성 검사
+    chkValidations() {
+      // 기본 정보
+      if (this.$isEmpty(this.job.lafCode)) {
+        this.$showAlert("경고", "송신기관 코드를 입력하지 않았습니다. 송신기관 코드를 입력해 주세요.");
+        return false;
+      }
+      if (this.$isEmpty(this.job.taskCode)) {
+        this.$showAlert("경고", "업무구분 코드를 입력하지 않았습니다. 업무구분 코드를 입력해 주세요.");
+        return false;
+      }
+      if (this.$isEmpty(this.job.tableName)) {
+        this.$showAlert("경고", "테이블 명을 입력하지 않았습니다. 테이블 명을 입력해 주세요.");
+        return false;
+      }
+      if (this.$isEmpty(this.job.ifId)) {
+        this.$showAlert("경고", "인터페이스 ID를 입력하지 않았습니다. 인터페이스 ID를 입력해 주세요.");
+        return false;
+      }
+
+      // 헤더 정보
+      for (let item of this.defaultHeaders) {
+        if (item.required) {
+          if (this.$isEmpty(item.value)) {
+            this.$showAlert("경고", "헤더 정보 중 필수 입력값을 입력하지 않았습니다. 필수 입력값을 입력해 주세요.");
+            return false;
+          }
+        }
+      }
+
+      return true;
+    },
+
+    // ##버튼
+    // 이전/다음 버튼
+    fnPrevNextPage() {
+      if (this.modalView == "default") {
+        if (!this.chkValidations()) { return; }
+        this.fnRun();
+      } else if (this.modalView == "preview") {
+        // defaultData 초기화
+        this.job.ifId = null;
+
+        this.dataTable = {};
+        this.rowKeys = [];
+        this.rowData = [];
+
+        // 모달창 변경
+        this.modalView = "default";
+      }
+    },
+    // 조회
+    fnRun() {
+      const vm = this;
+      // 실행
+      axios({
+        url: "/openfiscal.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: vm.job,
+      })
+        .then((response) => {
+          if (response.data.checkMessage.status == "0000") {
+            let result = response.data;
+            this.dataTable = result;
+            this.rowKeys = []; // 초기화
+            for (let columnData of result.columnDatas) {
+              this.rowKeys.push(columnData.columnNm);
+              // displayColumnNm 초기값 설정
+              columnData.displyColumnNm = columnData.orginlColumnNm;
+            }
+            this.rowData = result.rowData;
+
+            // 모달창 변경
+            this.modalView = "preview";
+          } else {
+            this.$showAlert("알람", response.data.checkMessage.message);
+          }
+        })
+        .catch((error) => {
+          vm.$showAlert("경고", "응답이 없습니다. 입력한 정보를 확인해 주세요.");
+        });
+    },
+    // 저장 버튼
+    fnSave() {
+      // 데이터 세팅
+      let jobItm = this.jobItem;
+      jobItm.itm = this.job;
+      jobItm.dataTable = this.dataTable;
+
+      console.log("@@", jobItm);
+      // 실행
+      this.$emit("fnSaveSetup", jobItm);
+    },
+  }
+}
+</script>
+<style scoped>
+.cbtnDelete {
+  width: 18px;
+  height: 18px;
+}
+
+.cbtnDelete img {
+  width: 100%;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/apiConnection.vue (added)
+++ client/views/component/connection/itm/apiConnection.vue
@@ -0,0 +1,441 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container large-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>API 연결</h2>
+          <button class="close-btn" @click="$emit('fnCloseModal')">
+            <svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="modal-content-monthly">
+        <div v-if="modalView == 'default'">
+          <div class="content-titleZone">
+            <p class="box-title">API 기본정보</p>
+          </div>
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 15%" />
+              <col style="width: 85%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>API URL</th>
+                <td>
+                  <input type="text" class="full-input" v-model="apiUrl" @change="fnApiUrlChange" />
+                </td>
+              </tr>
+              <tr>
+                <th>결과타입</th>
+                <td>
+                  <div class="flex">
+                    <div class="flex15 pl0">
+                      <input type="radio" name="json" id="json" value="1" class="chekck-type" v-model="jobItm.itm.type" />
+                      <label for="json" class="chekcktype-label text-ct"> json </label>
+                    </div>
+                    <div class="flex15">
+                      <input type="radio" name="xml" id="xml" value="2" class="chekck-type" v-model="jobItm.itm.type" />
+                      <label for="xml" class="chekcktype-label text-ct"> xml </label>
+                    </div>
+                    <div class="flex15 pr0">
+                      <input type="radio" name="seol" id="seol" value="3" class="chekck-type" v-model="jobItm.itm.type" />
+                      <label for="seol" class="chekcktype-label text-ct"> seol </label>
+                    </div>
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          <div class="content-titleZone flex justify-between align-center mt20">
+            <p class="box-title">파라미터 목록</p>
+            <div>
+              <button class="blue-border-btn small-btn" @click="fnAddRow()"> 파라미터 추가 </button>
+              <button v-if="apiUrl" class="blue-btn small-btn" @click="fnAutoCreate()"> 자동 생성 </button>
+            </div>
+          </div>
+          <div style="height: 400px; overflow: auto">
+            <table class="list-table" style="table-layout: fixed">
+              <colgroup>
+                <col width="10%" />
+                <col width="30%" />
+                <col width="50%" />
+                <col width="10%" />
+              </colgroup>
+              <thead>
+                <tr>
+                  <th style="text-align: center">No</th>
+                  <th style="text-align: center">Key</th>
+                  <th style="text-align: center">Value</th>
+                  <th style="text-align: center">삭제</th>
+                </tr>
+              </thead>
+              <tbody>
+                <template v-if="parameterList.length > 0">
+                  <tr v-for="(item, index) in parameterList" :key="index">
+                    <td style="text-align: center">{{ index + 1 }}</td>
+                    <td>
+                      <input type="text" class="full-input" v-model.trim="item.key" />
+                    </td>
+                    <td>
+                      <input type="text" class="full-input" v-model.trim="item.value" />
+                    </td>
+                    <td>
+                      <button class="cbtnDelete" @click="fnDeleteRow(item)">
+                        <img src="../../../../resources/img/delete.png" alt="삭제" />
+                      </button>
+                    </td>
+                  </tr>
+                </template>
+                <tr v-else>
+                  <td colspan="4">등록된 데이터가 없습니다.</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+        <div v-if="modalView == 'preview'">
+          <div class="content-titleZone flex justify-between align-center">
+            <p class="box-title">미리보기</p>
+            <button type="button" class="blue-border-btn small-btn" @click="fnDataKeySelect"> 조회 </button>
+          </div>
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 15%" />
+              <col style="width: 85%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>사용할 데이터</th>
+                <td>
+                  <select v-model="selectKey">
+                    <template v-if="apiResponseKeys.length > 0">
+                      <option value="">사용할 데이터를 선택하세요.</option>
+                      <option v-for="(key, index) of apiResponseKeys" :key="index" :value="key"> {{ key }} </option>
+                    </template>
+                    <option v-else value="">전체 조회</option>
+                  </select>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          <div class="content-titleZone flex justify-between align-center mt20">
+            <p class="box-title"> 데이터셋 <small> (총 ROW: {{ rowData.length > 1 ? rowData.length : 0 }}개) </small>
+            </p>
+          </div>
+          <table class="list-table table-flow">
+            <template v-if="!$isEmpty(dataTable)">
+              <thead>
+                <tr>
+                  <th>No</th>
+                  <th v-for="(rowKey, index) of rowKeys" :key="index"> {{ rowKey }} </th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(items, indexI) of rowData" :key="indexI">
+                  <td>{{ indexI + 1 }}</td>
+                  <td v-for="(item, indexJ) of items" :key="indexJ"> {{ item }} </td>
+                </tr>
+              </tbody>
+            </template>
+            <tr v-else>
+              <td>등록된 데이터가 없습니다.</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+      <div class="modal-end" style="text-align: right">
+        <button type="button" :class="{
+          'small-btn': true,
+          'blue-btn': modalView == 'default',
+          'blue-border-btn': modalView == 'preview',
+        }" @click="fnPrevNextPage">
+          <span v-if="modalView == 'preview'">이전</span>
+          <span v-if="modalView == 'default'">다음</span>
+        </button>
+        <button type="button" v-if="modalView == 'preview'" class="blue-btn small-btn" @click="fnSave"> 저장 </button>
+        <button type="button" class="blue-border-btn small-btn" @click="$emit('fnCloseModal')"> 취소 </button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiClose } from "@mdi/js";
+import ApiRusultCus from "./apiRusultCus.vue";
+import axios from "axios";
+
+export default {
+  components: {
+    SvgIcon: SvgIcon,
+    ApiRusultCus: ApiRusultCus,
+  },
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Boolean,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      isModalOpen: this.openPopup,
+      closePath: mdiClose,
+      jobItm: this.jobItem,
+
+      modalView: "default",
+
+      apiUrl: null,
+      parameterList: [],
+
+      apiResponse: null,
+      apiResponseKeys: [],
+      selectKey: "",
+
+      dataTable: {},
+
+      rowData: [],
+      rowKeys: [],
+    };
+  },
+  mounted() {
+    this.init();
+  },
+  methods: {
+    // 초기화 function
+    async init() {
+      this.modalView = "default";
+      this.apiUrl = null;
+      this.parameterList = [];
+      this.apiResponseKeys = [];
+      this.selectKey = "";
+      this.dataTable = {};
+      this.rowData = [];
+      this.rowKeys = [];
+
+      if (!this.$isEmpty(this.jobItm)) {
+        let item = this.jobItm.itm;
+        if (item.url != null && item.url != "") {
+          if (item.url.indexOf("?") != -1) {
+            item.url.substring(0, item.url.indexOf("?"));
+          }
+          let params = "";
+          for (let param of item.params) {
+            if (params != "") {
+              params += "&";
+            }
+            params += param.key + "=" + param.value;
+          }
+          this.apiUrl = item.url + "?" + params;
+
+          for (let param of item.params) {
+            this.parameterList.push({
+              index: param.index,
+              key: param.key,
+              value: param.value,
+              addMonth: 0,
+            });
+          }
+        }
+      } else {
+        this.jobItm = Object.assign({}, this.$getDefaultJobGroup().node);
+        this.jobItm.itm = Object.assign(
+          {},
+          this.$getDefaultJobGroup().connectionApi
+        );
+      }
+    },
+
+    // API URL 변경
+    fnApiUrlChange() {
+      this.parameterList = []; // 파라미터 목록 초기화
+
+      // 미리보기 페이지 데이터 초기화
+      this.selectKey = "";
+      this.dataTable = {};
+      this.rowData = [];
+      this.rowKeys = [];
+    },
+
+    // 파라미터 행 추가
+    fnAddRow() {
+      this.parameterList.push({
+        index: this.parameterList.length + 1,
+        key: null,
+        value: null,
+        addMonth: 0,
+      });
+    },
+    // 파라미터 행 삭제
+    fnDeleteRow(item) {
+      this.parameterList = this.parameterList.filter(
+        (parameter) => parameter !== item
+      );
+    },
+
+    // 자동 생성 버튼
+    async fnAutoCreate() {
+      // 덮어쓰기 경고
+      if (this.parameterList.length > 0) {
+        let isChecked = await this.$showConfirm(
+          "경고",
+          "자동생성 실행 시, 기존에 작성한 파라미터가 삭제됩니다. 그래도 실행하시겠습니까?"
+        );
+        if (!isChecked) {
+          return;
+        } else {
+          this.parameterList = []; // 파라미터 목록 초기화
+        }
+      }
+
+      // 실행
+      let apiUrl = this.apiUrl;
+      if (apiUrl) {
+        let parameters = apiUrl.substring(apiUrl.indexOf("?") + 1);
+        if (parameters.length > 1) {
+          let params = parameters.split("&");
+          for (let param of params) {
+            let paramIndex = param.indexOf("=");
+            this.parameterList.push({
+              index: this.parameterList.length + 1,
+              key: param.substring(0, paramIndex),
+              value: param.substring(paramIndex + 1),
+              addMonth: 0,
+            });
+          }
+        }
+      }
+    },
+    // 셀 크기
+    fnColWidth(keys) {
+      return `width: ${100 / (keys + 1)}%`;
+    },
+
+    // 이전/다음 버튼
+    fnPrevNextPage() {
+      if (this.modalView == "default") {
+        // 데이터 조회
+        this.apiRequestTest();
+      } else if (this.modalView == "preview") {
+        // 데이터 초기화
+        this.apiResponseKeys = [];
+        this.dataTable = {};
+        this.rowKeys = [];
+        this.rowData = [];
+
+        // 모달창 변경
+        this.modalView = "default";
+      }
+    },
+    // 다음 버튼
+    apiRequestTest() {
+      const vm = this;
+      if (vm.parameterList.length < 1) {
+        vm.$showAlert(
+          "경고",
+          "파라미터가 비어있습니다. 파라미터를 입력해 주세요."
+        );
+        return;
+      }
+      // 데이터 세팅
+      vm.jobItm.itm.url = vm.apiUrl.substring(0, vm.apiUrl.indexOf("?"));
+      vm.jobItm.itm.params = vm.parameterList;
+      // 실행
+      axios({
+        url: "/apiRequestTest.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: vm.jobItm.itm,
+      })
+        .then((response) => {
+          vm.apiResponseKeys = response.data.key;
+          vm.modalView = "preview"; // 모달창 변경
+        })
+        .catch((error) => {
+          vm.$showAlert(
+            "결과내용",
+            "응답이 없습니다. 파라미터를 확인해 주세요."
+          );
+        });
+    },
+    getType(target) {
+      return Object.prototype.toString.call(target).slice(8, -1);
+    },
+    // 조회 버튼
+    fnDataKeySelect() {
+      if (this.apiResponseKeys.length > 0 && (this.selectKey == null || this.selectKey == "")) {
+        this.$showAlert("경고", "사용할 데이터를 선택해 주세요.");
+        return;
+      }
+      this.jobItm.itm.depth = this.selectKey;
+
+      // 데이터 세팅
+      this.jobItm.itm.url = this.apiUrl.substring(0, this.apiUrl.indexOf("?"));
+
+      // 실행
+      axios({
+        url: "/getApiDataTable.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: this.jobItm.itm,
+      })
+        .then((response) => {
+          let result = response.data;
+          this.dataTable = result;
+          this.rowKeys = []; // 초기화
+          for (let columnData of result.columnDatas) {
+            this.rowKeys.push(columnData.columnNm);
+            // displayColumnNm 초기값 설정
+            columnData.displyColumnNm = columnData.orginlColumnNm;
+          }
+          this.rowData = result.rowData;
+        })
+        .catch((error) => {
+          this.$showAlert(
+            "결과내용",
+            "응답이 없습니다. 데이터를 확인해 주세요."
+          );
+        });
+    },
+    // 저장 버튼
+    fnSave() {
+      // 유효성 검사
+      if (this.$isEmpty(this.apiUrl)) {
+        this.$showAlert("경고", "API URL을 입력해 주세요.");
+        return false;
+      }
+      if (this.$isEmpty(this.dataTable)) {
+        this.$showAlert("경고", "사용할 데이터를 조회해 주세요.");
+        return false;
+      }
+      // 데이터 세팅
+      this.jobItm.dataTable = this.dataTable;
+      // 실행
+      let jobItem = this.jobItm;
+      this.$emit("fnSaveSetup", jobItem);
+    },
+  },
+};
+</script>
+<style scoped>
+.cbtnDelete {
+  width: 18px;
+  height: 18px;
+}
+
+.cbtnDelete img {
+  width: 100%;
+}
+
+.table-flow {
+  width: 100%;
+  word-wrap: break-word;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/apiRusultCus.vue (added)
+++ client/views/component/connection/itm/apiRusultCus.vue
@@ -0,0 +1,19 @@
+<template>
+  <div v-for="(item, index) in apiResponseResult" :key="index">
+    <p>
+      <span @click="$emit('fnJobItmDepth', item.key)">{{ item.key }}</span>
+      <span>{{ item.value }}</span>
+    </p>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    apiResponseResult: {
+      type: Object,
+      default: null,
+    },
+  },
+};
+</script>
 
client/views/component/connection/itm/dataCheck.vue (added)
+++ client/views/component/connection/itm/dataCheck.vue
@@ -0,0 +1,146 @@
+<template>
+    <div v-show="isModalOpen" class="modal-wrapper">
+        <div class="modal-container">
+            <div class="modal-title">
+                <div class="flex justify-between align-center">
+                    <h2>센서 알람 설정</h2>
+                    <button class="close-btn" @click="closeModal"><svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon></button>
+                </div>
+            </div>
+            <div class="modal-content-monthly">
+                <div class="flex justify-between align-center">
+                    <div class="count-zone">
+                        <p>총 <span>{{this.jobItem.itm.dataCheckItems.length}}</span>건 중 <span>{{selectCount}}</span>건 선택</p>
+                    </div>
+                    <div class="cunt-selectZone">
+                        <p>중복알람 주기설정</p>
+                        <select v-model="jobItm.itm_option_string">
+                            <option value="1">1분</option>
+                            <option value="5">5분</option>
+                        </select>
+                    </div>
+                </div>
+                <div class="table-zone">
+                    <table class="list-table">
+                        <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+                        <colgroup>
+                            <col style="width: 22.5%;">
+                            <col style="width: 22.5%;">
+                            <col style="width: 22.5%;">
+                            <col style="width: 22.5%;">
+                            <col style="width: 10%;">
+                        </colgroup>
+                        <thead>
+                            <tr>
+                                <th>컬럼명</th>
+                                <th>한글명</th>
+                                <th>MIN</th>
+                                <th>MAX</th>
+                                <th>사용여부</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr v-for="(item, indx) in jobItem.itm.dataCheckItems" :key="indx">
+                                <td>{{item.column_display}}</td>
+                                <td><input type="text" class="full-input" v-model="item.column_origin"/></td>
+                                <td><input type="text" class="full-input"  v-model="item.min_value"/></td>
+                                <td><input type="text" class="full-input"  v-model="item.max_value"/></td>
+                                <td><input type="checkbox" v-model="item.use_at"></td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-end flex justify-end">
+                <button class="orange-btn small-btn" @click="closeModal">닫기</button>
+                <button class="blue-btn small-btn" @click="closeModal">확인</button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import SvgIcon from '@jamescoyle/vue-icon';
+import { mdiClose } from '@mdi/js';
+
+export default {
+    name: 'data-check',
+    props: {
+        openPopup: {
+            type: Boolean,
+            default: false
+        },
+        jobItem: {
+            type: Boolean,
+            default: null
+        }
+    },
+    data() {
+        return {
+            isModalOpen: this.openPopup
+            , jobItm: this.jobItem
+            , closePath:mdiClose
+        }
+    },
+    methods: {
+        // 모달 닫기
+        closeModal: function () {
+            this.isModalOpen = false;
+            this.$emit("closePopup", this.jobItm.indx);
+        },
+
+        dataSetting : function(){
+            if(this.jobItm.itm.dataCheckItems.length == 0 ){
+                for(let i = 0 ; i < this.jobItm.front_dataTable.columnDatas.length ; i++){
+                    if(this.jobItm.front_dataTable.columnDatas[i].dataTy == 'INT' ||
+                        this.jobItm.front_dataTable.columnDatas[i].dataTy  ==  'LONG' ||
+                        this.jobItm.front_dataTable.columnDatas[i].dataTy == 'FLOAT' ||
+                        this.jobItm.front_dataTable.columnDatas[i].dataTy == 'DOUBLE'){
+                        let DataCheckItem = {};
+                        DataCheckItem.order = i;
+                        DataCheckItem.column_origin = this.jobItm.front_dataTable.columnDatas[i].columnNm;
+                        DataCheckItem.column_display = this.jobItm.front_dataTable.columnDatas[i].displyColumnNm;
+                        DataCheckItem.min_value = 0.0;
+                        DataCheckItem.max_value = 1.0;
+                        DataCheckItem.use_at = true;
+                        this.jobItem.itm.dataCheckItems.push(DataCheckItem)
+                    }
+                }
+            }
+        },
+
+        resetData : function(){
+            this.jobItm.front_dataTable.columnDatas = [];
+            this.dataSetting();
+        }
+    },
+    watch: {
+        openPopup: function (v) {
+            this.isModalOpen = v;
+        },
+
+        jobItem: function (v) {
+            this.jobItm = v;
+            this.dataSetting();
+        },
+    },
+    computed: {
+        selectCount: function () {
+            let count = 0;
+            for(let i = 0 ; i < this.jobItem.itm.dataCheckItems.length ; i++){
+                if(this.jobItem.itm.dataCheckItems[i].use_at){
+                    count++;
+                }
+            }
+            return count;
+        }
+    },
+    components: {
+        'SvgIcon':SvgIcon
+    },
+    mounted() {
+        this.dataSetting();
+
+    }
+}
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/dataFilter.vue (added)
+++ client/views/component/connection/itm/dataFilter.vue
@@ -0,0 +1,263 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container large-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>데이터 필터</h2>
+          <button type="button" class="close-btn" @click="$emit('fnCloseModal')">
+            <svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="flex modal-content-monthly">
+        <div class="flex50 pl0">
+          <div class="flex justify-between">
+            <div class="input-container flex">
+              <label class="radio-label mr5">
+                <input type="radio" :name="comId + 'match'" class="custom-radiobox" :value="true" v-model="jobItm.itm.match_type" />
+                <span>AND 연산</span>
+              </label>
+              <label class="radio-label">
+                <input type="radio" :name="comId + 'match'" class="custom-radiobox" :value="false" v-model="jobItm.itm.match_type" />
+                <span>OR 연산</span>
+              </label>
+            </div>
+            <button class="blue-btn small-btn" @click="fnAddFiter"> 필터 추가 </button>
+          </div>
+          <div class="table-zone">
+            <table class="form-table">
+              <thead>
+                <tr>
+                  <th>컬럼</th>
+                  <th>연산</th>
+                  <th>내용</th>
+                  <th>관리</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(filter, index) in filterList" :key="index" class="text-ct">
+                  <td>
+                    <select class="full-input" v-model="filter.coulmn_nm" @change="fnChangeColumn(index)">
+                      <option v-for="(itemCol, j) in itemColList" :key="j" :value="itemCol.orginlColumnNm"> {{ itemCol.displyColumnNm }} </option>
+                    </select>
+                  </td>
+                  <td>
+                    <select class="full-input" v-model="filter.calc_ty">
+                      <option value="">선택해주세요</option>
+                      <option value="1">=</option>
+                      <option v-if="filter.data_ty != 'STRING'" value="2"> &lt; </option>
+                      <option v-if="filter.data_ty != 'STRING'" value="3"> &gt; </option>
+                      <option v-if="filter.data_ty != 'STRING'" value="4"> &lt;= </option>
+                      <option v-if="filter.data_ty != 'STRING'" value="5"> &gt;= </option>
+                      <option value="6">!=</option>
+                      <option v-if="filter.data_ty == 'STRING'" value="7"> 포함 </option>
+                    </select>
+                  </td>
+                  <td>
+                    <input type="text" class="full-input" v-model="filter.cmpr_value" @input="fnChangeValue($event, index)" />
+                  </td>
+                  <td>
+                    <button class="blue-border-btn small-btn" @click="fnDeleteFilter(index)"> 삭제 </button>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+        <div class="flex50 pr0">
+          <div class="content-titleZone flex justify-between align-center">
+            <p class="box-title">미리보기</p>
+            <button class="blue-border-btn small-btn" @click="fnRun"> 조회 </button>
+          </div>
+          <table class="list-table" v-if="!$isEmpty(resultJobItm.dataTable)">
+            <thead>
+              <tr>
+                <th style="min-width: 60px">No</th>
+                <th v-for="(columnData, index) of resultJobItm.dataTable
+                  .columnDatas" :key="index"> {{ columnData.orginlColumnNm }} </th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr v-for="(row, i) of resultJobItm.dataTable.rowData" :key="i">
+                <td style="min-width: 60px">{{ i + 1 }}</td>
+                <td v-for="(rowKey, j) of row" :key="j"> {{ rowKey }} </td>
+              </tr>
+            </tbody>
+          </table>
+          <table class="list-table" v-else>
+            <tr>
+              <td>등록된 데이터가 없습니다.</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button type="button" class="blue-btn small-btn" @click="fnSave"> 저장 </button>
+        <button type="button" class="blue-border-btn small-btn" @click="$emit('fnCloseModal')"> 닫기 </button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiClose } from "@mdi/js";
+import axios from "axios";
+
+export default {
+  components: {
+    SvgIcon: SvgIcon,
+  },
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Object,
+    },
+    nodes: {
+      type: Array,
+      required: true,
+    },
+    edges: {
+      type: Array,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      isModalOpen: this.openPopup,
+      closePath: mdiClose,
+      jobItm: this.jobItem,
+      nodeList: this.nodes,
+      edgeList: this.edges,
+
+      comId:
+        "comId_" +
+        Math.random().toString(36).substring(2, 15) +
+        Math.random().toString(36).substring(2, 15),
+
+      prevNode: {},
+      filterList: [],
+
+      resultJobItm: {},
+    };
+  },
+  mounted() {
+    // 첫 실행시
+    this.fnFindPrevNode(); // 이전 노드 정보 가져오기
+    if (this.jobItm.itm.hasOwnProperty("filterItems")) {
+      if (this.jobItm.itm.filterItems.length > 0) {
+        this.filterList = this.jobItm.itm.filterItems;
+        this.fnRun();
+      }
+    }
+  },
+  methods: {
+    // 이전 노드 정보 찾기
+    fnFindPrevNode() {
+      // 1. 연결된 이전 노드 아이디 구하기
+      loop: for (let edge of this.edgeList) {
+        if (edge.target == this.jobItm.id) {
+          // 2. 노드 아이디(edge.source)로 노드 구하기
+          for (let node of this.nodeList) {
+            if (node.id == edge.source) {
+              this.prevNode = node;
+              // 3-1. 이전 노드의 설정을 마친 경우
+              if (!this.$isEmpty(this.prevNode.dataTable)) {
+                this.itemColList = this.prevNode.dataTable.columnDatas;
+              }
+              // 3-2. 예외
+              else {
+                this.$showAlert(
+                  "에러 발생",
+                  "DATA_FILTER에 연결된 노드의 설정을 마쳐 주세요."
+                );
+                this.$emit("fnCloseModal"); // 모달 닫기
+              }
+              break loop;
+            }
+          }
+        }
+      }
+      // 3. 예외
+      if (this.$isEmpty(this.prevNode)) {
+        this.$showAlert(
+          "에러 발생",
+          "DATA_FILTER를 설정하기 전, 먼저 노드를 연결해 주세요."
+        );
+        this.$emit("fnCloseModal"); // 모달 닫기
+      }
+    },
+
+    // 필터 추가 버튼
+    fnAddFiter() {
+      let filter = {
+        coulmn_nm: null,
+        calc_ty: 1,
+        data_ty: null,
+        cmpr_value: null,
+      };
+      this.filterList.push(filter);
+    },
+
+    /* 내용 변경 감지 */
+    // 컬럼
+    fnChangeColumn(index) {
+      this.filterList[index].calc_ty = ""; // 내용이 변경되면 연산 초기화
+      this.filterList[index].cmpr_value = ""; // 내용이 변경되면 내용 초기화
+    },
+    // 내용
+    fnChangeValue(event, index) {
+      let item = event.target.value;
+      let type = typeof item;
+      if (Number(type) != NaN) {
+        type = "number";
+      }
+      this.filterList[index].data_ty = type.toUpperCase();
+      this.filterList[index].calc_ty = ""; // 내용이 변경되면 연산 초기화
+    },
+
+    // row 삭제 버튼
+    fnDeleteFilter(index) {
+      this.filterList.splice(index, 1);
+    },
+
+    // 조회 버튼
+    fnRun() {
+      // 데이터 세팅
+      let data = this.jobItm;
+      data.itm.filterItems = this.filterList;
+      data.dataTable = this.prevNode.dataTable;
+      // 실행
+      axios({
+        url: "/diagram/runFilter.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: data,
+      })
+        .then((response) => {
+          this.resultJobItm = response.data;
+        })
+        .catch((error) => {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+
+    // 저장 버튼
+    fnSave() {
+      if (this.$isEmpty(this.resultJobItm)) {
+        this.$showAlert(
+          "에러 발생",
+          "조회 결과가 없으면 저장할 수 없습니다. 조회 버튼을 눌러 주세요."
+        );
+        return;
+      }
+      this.$emit("fnSaveSetup", this.resultJobItm);
+    },
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/dataSort.vue (added)
+++ client/views/component/connection/itm/dataSort.vue
@@ -0,0 +1,150 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container" style="height: auto">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>데이터 정렬</h2>
+          <button class="close-btn" @click="closeModal">
+            <svg-icon
+              type="mdi"
+              :width="20"
+              :height="20"
+              :path="closePath"
+            ></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="modal-content-monthly">
+        <div class="table-zone">
+          <table class="form-table">
+            <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+            <colgroup>
+              <col style="width: 20%" />
+              <col style="width: 80%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>정렬기준</th>
+                <td>
+                  <select
+                    name="ctgry"
+                    id="ctgry"
+                    class="full-input"
+                    style="width: 100%"
+                    @change="dataInsert($event)"
+                  >
+                    <option
+                      v-for="(temp, j) in item.front_dataTable.columnDatas"
+                      :key="j"
+                      :value="j"
+                    >
+                      {{ temp.orginlColumnNm }}
+                    </option>
+                  </select>
+                </td>
+              </tr>
+              <tr>
+                <th>정렬방법</th>
+                <td>
+                  <div class="input-container flex">
+                    <label class="radio-label">
+                      <input
+                        type="radio"
+                        name="cmprValue"
+                        class="custom-radiobox"
+                        value="ASC"
+                        v-model="item.itm.filterItems[0].cmpr_value"
+                      />
+                      <span>오름차순</span>
+                    </label>
+                    <label class="radio-label">
+                      <input
+                        type="radio"
+                        name="cmprValue"
+                        checked
+                        class="custom-radiobox"
+                        value="DESC"
+                        v-model="item.itm.filterItems[0].cmpr_value"
+                      />
+                      <span>내림차순</span>
+                    </label>
+                  </div>
+                </td>
+              </tr>
+              <tr>
+                <th>최대조회수</th>
+                <td>
+                  <input
+                    type="text"
+                    class="full-input"
+                    v-model="item.itm.filterItems[0].cmpr_value2"
+                  />
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button class="orange-btn small-btn" @click="closeModal">닫기</button>
+        <button class="blue-btn small-btn" @click="closeModal">확인</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "data-check",
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    item: {
+      type: Object,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      isModalOpen: this.openPopup,
+      itm: this.item,
+    };
+  },
+  methods: {
+    // 모달 닫기
+    closeModal: function () {
+      this.isModalOpen = false;
+      this.$emit("closePopup", this.item.indx);
+    },
+
+    dataInsert: function (event) {
+      this.item.itm.filterItems[0].coulmn_nm =
+        this.item.front_dataTable.columnDatas[
+          event.target.value
+        ].orginlColumnNm;
+      this.item.itm.filterItems[0].data_ty =
+        this.item.front_dataTable.columnDatas[event.target.value].dataTy;
+    },
+  },
+  watch: {
+    openPopup: function (v) {
+      this.isModalOpen = v;
+    },
+
+    item: function (v) {
+      this.itm = v;
+    },
+  },
+  mounted() {
+    // 데이터 초기화
+    if (this.item.itm.filterItems[0].coulmn_nm == null) {
+      this.item.itm.filterItems[0].coulmn_nm =
+        this.item.front_dataTable.columnDatas[0].orginlColumnNm;
+      this.item.itm.filterItems[0].data_ty =
+        this.item.front_dataTable.columnDatas[0].dataTy;
+    }
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/databaseConnection.vue (added)
+++ client/views/component/connection/itm/databaseConnection.vue
@@ -0,0 +1,535 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container large-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>데이터베이스 연결</h2>
+          <button class="close-btn" @click="$emit('closePopup')">
+            <svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="modal-content-monthly" v-show="currentPage == 1">
+        <div class="table-zone">
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 25%" />
+              <col style="width: 75%" />
+            </colgroup>
+            <tbody v-if="jobItm.itm != null">
+              <tr>
+                <th>연계정보 타입</th>
+                <td>
+                  <div class="input-container flex">
+                    <label class="radio-label">
+                      <input type="radio" name="radio" :value="false" class="custom-radiobox" @change="successAt = false" v-model="jobItm.itm_option_bool" />
+                      <span>직접입력</span>
+                    </label>
+                    <label class="radio-label">
+                      <input type="radio" name="radio" :value="true" class="custom-radiobox" @change="successAt = false" v-model="jobItm.itm_option_bool" />
+                      <span>불러오기</span>
+                    </label>
+                  </div>
+                </td>
+              </tr>
+              <tr v-show="jobItm.itm_option_bool">
+                <th>연계정보</th>
+                <td>
+                  <input type="text" class="half-input" disabled :value="linkConnectionDB.conectNm +
+                    '(' +
+                    linkConnectionDB.conectIp +
+                    ')'
+                    " v-if="linkConnectionDB.conectNm != null" />
+                  <input type="text" class="half-input" disabled v-else />
+                  <button class="blue-border-btn small-btn" @click="dbConSearchOpen(true)"> 검색 </button>
+                </td>
+              </tr>
+              <tr>
+                <th>DMBS</th>
+                <td>
+                  <select id="databaseType" @change="successAt = false" class="square-select half-input" v-model="inputConnectionDB.databaseType" :disabled="jobItm.itm_option_bool">
+                    <option v-for="(itm, index) in databaseTypeList" :key="index" :value="itm.key"> {{ itm.value }} </option>
+                  </select>
+                </td>
+              </tr>
+              <tr>
+                <th>IP</th>
+                <td>
+                  <input id="conectIp" type="text" @input="successAt = false" class="half-input" v-model="inputConnectionDB.conectIp" placeholder="127.0.0.1" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>PORT</th>
+                <td>
+                  <input id="conectPort" type="text" @input="successAt = false" class="half-input" v-model="inputConnectionDB.conectPort" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>DB 명</th>
+                <td>
+                  <input id="databaseNm" type="text" @input="successAt = false" class="half-input" v-model="inputConnectionDB.databaseNm" placeholder="데이터베이스명 OR 스키마명" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>접속ID</th>
+                <td>
+                  <input type="text" class="half-input" @input="successAt = false" v-model="inputConnectionDB.userId" placeholder="접속 ID" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+              <tr>
+                <th>접속PW</th>
+                <td>
+                  <input type="password" class="half-input" @input="successAt = false" v-model="inputConnectionDB.userPassword" placeholder="접속 PW" autocomplete="new-password" :disabled="jobItm.itm_option_bool" />
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          <div class="content-titleZone flex justy justify-between align-center mt20">
+            <p class="box-title">데이터베이스 연결 결과</p>
+            <button class="blue-border-btn small-btn" @click="dataBaseConnectionCheck()"> 접속확인 </button>
+          </div>
+          <div class="table-zone">
+            <table class="list-table">
+              <colgroup>
+                <col width="10%" />
+                <col width="10%" />
+                <col width="80%" />
+              </colgroup>
+              <thead>
+                <tr>
+                  <th>No</th>
+                  <th>접속시간</th>
+                  <th>접속결과</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(itm, indx) in resultMessage" :key="indx">
+                  <td>{{ indx + 1 }}</td>
+                  <td>{{ itm.time }}</td>
+                  <td>{{ itm.message }}</td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+      <div class="modal-content-monthly" v-show="currentPage == 2">
+        <Splitter style="height: 100%; border: 0px solid #e5e7eb">
+          <SplitterPanel class="flex align-items-center justify-content-center" :size="20" :minSize="10">
+            <div class="content-box">
+              <div class="file-zone">
+                <div class="content-titleZone" style="height: 60px">
+                  <p class="box-title">table 정보</p>
+                </div>
+                <div class="content-zone2">
+                  <ul class="content-list" v-if="tableList.length > 0">
+                    <li class="cursor" v-for="(item, indx) in tableList" :key="indx">
+                      <a @click="getTableData(item)" :class="{
+                        'file-list': true,
+                        selected: selectTable === item,
+                      }">
+                        <div class="flex align-center">
+                          <!-- <p>{{ item.tableNmKr != null && item.tableNmKr != '' ? item.tableNmKr : '-' }} <span style="opacity: .6;">({{ item.tableNm }})</span></p> -->
+                          <p>{{ item.tableNmKr != null && item.tableNmKr != '' ? item.tableNmKr : item.tableNm }}</p>
+                        </div>
+                      </a>
+                    </li>
+                  </ul>
+                </div>
+              </div>
+            </div>
+          </SplitterPanel>
+          <SplitterPanel class="flex align-items-center justify-content-center" :size="80">
+            <div class="content-box">
+              <div class="content-titleZone" style="height: 60px">
+                <div class="flex justify-between aling-center">
+                  <p class="box-title">쿼리 작업</p>
+                  <div>
+                    <button class="icon-btn" @click="executeQuery" title="실행">
+                      <svg-icon type="mdi" :path="playPath" :color="'#fbbe28'"></svg-icon>
+                    </button>
+                  </div>
+                </div>
+              </div>
+              <div class="content-zone">
+                <Splitter style="height: 100%" layout="vertical">
+                  <SplitterPanel class="flex align-items-center justify-content-center" :size="50" :minSize="20">
+                    <textarea style="
+                        resize: none;
+                        max-width: 100%;
+                        max-height: 100%;
+                        padding: 10px;
+                      " v-model="jobItm.itm.query"></textarea>
+                  </SplitterPanel>
+                  <SplitterPanel class="align-items-center justify-content-center" :size="50">
+                    <ul class="tab-nav flex justify-start">
+                      <li @click="showTab('tab1')">
+                        <a href="#tab01" :class="{ activeTab: activeTab === 'tab1' }">작업결과</a>
+                      </li>
+                      <li @click="showTab('tab2')">
+                        <a href="#tab02" :class="{ activeTab: activeTab === 'tab2' }">작업log</a>
+                      </li>
+                    </ul>
+                    <div v-show="activeTab === 'tab1'" style="
+                        height: calc(100% - 60px);
+                        overflow: auto;
+                        padding: 10px;
+                      ">
+                      <div class="count-zone" v-if="jobItm.dataTable.columnDatas.length > 0">
+                        <p> 총 <span>{{ jobItm.dataTable.totalRows }}</span>건 중 <span>{{ jobItm.dataTable.rowData.length }}</span>건 조회 </p>
+                      </div>
+                      <div class="table-zone">
+                        <table class="list-table">
+                          <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+                          <thead>
+                            <tr v-if="jobItm.dataTable.columnDatas.length > 0">
+                              <th v-for="(itm, indx) in jobItm.dataTable
+                                .columnDatas" :key="indx" style="min-width: 150px !important"> {{ itm.columnNm }} <label class="check-label">
+                                  <input type="checkbox" class="custom-checkbox" v-model="itm.pkAt" />
+                                </label>
+                              </th>
+                            </tr>
+                          </thead>
+                          <tbody v-if="jobItm.dataTable.rowData.length > 0">
+                            <tr v-for="(row, rows) in jobItm.dataTable.rowData" :key="rows">
+                              <td v-for="(itm, indx) in row" :key="indx" style="
+                                  overflow: hidden;
+                                  white-space: nowrap;
+                                  text-overflow: ellipsis;
+                                "> {{ itm }} </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </div>
+                    </div>
+                    <div v-show="activeTab === 'tab2'" style="
+                        height: calc(100% - 60px);
+                        overflow: auto;
+                        padding: 10px;
+                      ">
+                      <div class="table-zone">
+                        <table class="list-table">
+                          <colgroup>
+                            <col width="10%" />
+                            <col width="10%" />
+                            <col width="" />
+                            <col width="10%" />
+                          </colgroup>
+                          <thead>
+                            <tr>
+                              <th>No</th>
+                              <th>접속시간</th>
+                              <th>접속결과</th>
+                              <th>접속내용</th>
+                            </tr>
+                          </thead>
+                          <tbody>
+                            <tr v-for="(itm, indx) in executeMessage" :key="indx">
+                              <td>{{ indx + 1 }}</td>
+                              <td>{{ itm.time }}</td>
+                              <td>{{ itm.message }}</td>
+                              <td>{{ itm.result }}</td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </div>
+                    </div>
+                  </SplitterPanel>
+                </Splitter>
+              </div>
+            </div>
+          </SplitterPanel>
+        </Splitter>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button class="blue-border-btn small-btn" @click="setPage(1)" v-show="currentPage == 2">이전</button>
+        <button class="blue-border-btn small-btn" @click="setPage(2)" v-show="currentPage == 1" :disabled="!successAt">다음</button>
+        <button class="blue-btn small-btn" @click="handleSaveNodeData(jobItm)" v-show="currentPage == 2">확인</button>
+        <button class="blue-border-btn small-btn" @click="$emit('closePopup')">취소</button>
+      </div>
+    </div>
+    <DBConSearch :openPopup="openSearchModal" @modalclose="dbConSearchOpen" @selectItm="selectDbcon" />
+  </div>
+</template>
+<script>
+import axios from "axios";
+import SvgIcon from "@jamescoyle/vue-icon";
+import DBConSearch from "../../dataComponent/DbConnectionSearchModal.vue";
+import { mdiClose, mdiPlay, mdiCancel } from "@mdi/js";
+
+export default {
+  name: "database-connection",
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Boolean,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      closePath: mdiClose,
+      // 현재 모달창 관리
+      isModalOpen: this.openPopup,
+      // 연계정보 불러오기
+      openSearchModal: false,
+      // 커넥션 DB 오브젝트
+      jobItm: this.jobItem,
+      connectionDB: {},
+      linkConnectionDB: {},
+      inputConnectionDB: {},
+      resultMessage: [],
+      executeMessage: [],
+      // 연계 성공여부
+      successAt: false,
+      databaseTypeList: [],
+      currentPage: 1,
+      tableList: [],
+      selectTable: {},
+      activeTab: "tab1",
+      playPath: mdiPlay,
+      cancelPath: mdiCancel,
+    };
+  },
+  methods: {
+    // 화면전환
+    setPage: function (val) {
+      this.currentPage = val;
+      if (val == 2) {
+        this.getDbConnectionTableList();
+      }
+    },
+    handleSaveNodeData(jobItm) {
+      this.$emit('saveNodeData', jobItm);
+      this.$emit('closePopup');
+    },
+    /***************************************************접속정보 설정************************************************************************/
+    // 연계가능 여부 테스트
+    dataBaseConnectionCheck: function () {
+      var vm = this;
+      if (this.jobItm.itm_option_bool == true) {
+        this.jobItm.itm = this.linkConnectionDB;
+      } else {
+        this.jobItm.itm = this.inputConnectionDB;
+      }
+      this.jobItm.itm.loadAt = this.jobItm.itm_option_bool;
+      delete this.jobItm.itm.type;
+
+      axios({
+        url: "/data/dataBaseConnectionCheck.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: vm.jobItm.itm,
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.success == true) {
+            vm.successAt = true;
+          } else {
+            vm.successAt = false;
+          }
+          vm.$showAlert("결과내용", response.data.checkMessage.message);
+          vm.resultMessage.push({
+            time: vm.$getFullTime(),
+            message: response.data.checkMessage.message,
+            result: response.data.checkMessage.success
+              ? "접속성공"
+              : "접속실패",
+          });
+        })
+        .catch(function (error) { });
+    },
+
+    // db커넥션 선택창 호출
+    dbConSearchOpen: function (val) {
+      this.openSearchModal = val;
+    },
+
+    // 연계정보 받기
+    selectDbcon: function (dbcon) {
+      this.linkConnectionDB = dbcon;
+    },
+    /***************************************************접속정보 설정 끝*******************************************************************/
+    /***************************************************데이터 베이스 설정*******************************************************************/
+    // 탭 변경
+    showTab: function (tabName) {
+      this.activeTab = tabName;
+    },
+    //DB 접속 - 테이블 목록 조회
+    getDbConnectionTableList: function () {
+      let vm = this;
+      axios({
+        url: "/data/selectTableList.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: vm.jobItm.itm,
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.success == true) {
+            vm.tableList = response.data.resultData.tableList;
+          }
+        })
+        .catch(function (error) { });
+    },
+
+    // 테이블 클릭
+    clickTable: function (itm) {
+      this.selectTable = itm;
+    },
+
+    // 선택한 테이블 정보 가져오기
+    getTableData: function (itm) {
+      let vm = this;
+      itm.perPage = this.jobItm.dataTable.perPage;
+
+      // 통신시 데이터 형문제로 삭제
+      this.jobItm.itm.creatDt = null;
+      let requestData = {
+        dataset: itm,
+        connectionDB: this.jobItm.itm,
+      };
+
+      axios({
+        url: "/data/getTableData.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: requestData,
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.success) {
+            vm.jobItm.dataTable = response.data.resultData.dataTable;
+            vm.jobItm.itm.query = vm.jobItm.dataTable.query;
+
+            vm.columnDatas = response.data.resultData.dataTable.columnDatas;
+            vm.jobItm.front_dataTable.columnDatas = vm.columnDatas;
+          }
+          vm.executeMessage.push({
+            time: vm.$getFullTime(),
+            message: response.data.resultData.dataTable.checkMessage.message,
+            result: response.data.resultData.dataTable.checkMessage.success
+              ? "성공"
+              : "실패",
+          });
+        })
+        .catch(function (error) { });
+    },
+
+    // 쿼리 실행
+    executeQuery: function () {
+      let vm = this;
+      // 통신시 데이터 형문제로 삭제
+      this.jobItm.itm.creatDt = null;
+      this.jobItm.dataTable.query = this.jobItm.itm.query;
+      let requestData = {
+        dataTable: this.jobItm.dataTable,
+        connectionDB: this.jobItm.itm,
+      };
+
+      axios({
+        url: "/data/getTableDataByQuery.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: requestData,
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.success) {
+            vm.jobItm.dataTable = response.data.resultData.dataTable;
+            vm.jobItm.itm.query = vm.jobItm.dataTable.query;
+          }
+          vm.executeMessage.push({
+            time: vm.$getFullTime(),
+            message: response.data.resultData.dataTable.checkMessage.message,
+            result: response.data.resultData.dataTable.checkMessage.success
+              ? "성공"
+              : "실패",
+          });
+        })
+        .catch(function (error) { });
+    },
+
+    /***************************************************데이터 베이스 설정끝*******************************************************************/
+
+    // 초기화 function
+    init: async function () {
+      this.databaseTypeList = await this.$getDataBaseTypeList();
+
+      if (this.jobItm == null) {
+        this.jobItm = Object.assign({}, this.$getDefaultJobGroup().node);
+        this.linkConnectionDB = Object.assign(
+          {},
+          this.$getDefaultJobGroup().connectionDb
+        );
+        this.inputConnectionDB = Object.assign(
+          {},
+          this.$getDefaultJobGroup().connectionDb
+        );
+        this.jobItm.itm = Object.assign(
+          {},
+          this.$getDefaultJobGroup().connectionDb
+        );
+      } else {
+        if (this.jobItm.itm_option_bool) {
+          this.linkConnectionDB = this.jobItm.itm;
+          this.connectionDB = this.linkConnectionDB;
+        } else {
+          this.inputConnectionDB = this.jobItm.itm;
+          this.connectionDB = this.inputConnectionDB;
+        }
+      }
+
+      if (this.inputConnectionDB.databaseType == null) {
+        this.inputConnectionDB.databaseType = this.databaseTypeList.MARIADB.key;
+      }
+    },
+  },
+  watch: {
+    openPopup: function (v) {
+      this.isModalOpen = v;
+    },
+
+    jobItem: function (v) {
+      this.jobItm = v;
+      if (v.itm_option_bool) {
+        this.linkConnectionDB = this.jobItm.itm;
+      } else {
+        this.inputConnectionDB = this.jobItm.itm;
+      }
+    },
+  },
+  components: {
+    DBConSearch: DBConSearch,
+    SvgIcon: SvgIcon,
+  },
+  mounted() {
+    this.init();
+  },
+  create() {
+    if (this.jobItm == null) {
+      this.jobItm = Object.assign({}, this.defaultJobItm);
+      this.jobItm.itm = this.$getDefaultJobGroup().connectionDb;
+    }
+  },
+};
+</script>
+<style>
+.content-zone2 {
+  height: calc(100% - 57px);
+  /* 기존 스타일 유지 */
+  max-height: calc(100% - 57px);
+  /* 최대 높이 설정 */
+  overflow-y: auto;
+  /* 수직 스크롤 추가 */
+  overflow-x: hidden;
+  /* 수평 스크롤 숨기기 (원하는 경우) */
+}
+</style>
 
client/views/component/connection/itm/datasetSelecter.vue (added)
+++ client/views/component/connection/itm/datasetSelecter.vue
@@ -0,0 +1,281 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>데이터 셋 선택</h2>
+          <button class="close-btn" @click="$emit('closePopup')">
+            <svg-icon type="mdi" :width="20" :height="20" :path="closePath"></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="content-wrap">
+        <div class="content-box flex justify-between">
+          <div class="flex20 content-box">
+            <div class="left-content flex100 content-box">
+              <CodeList :groupCode="'DATA_CTGRY'" @selectCode="selectCode" />
+            </div>
+          </div>
+          <div class="flex80 content-box">
+            <div class="right-content content-box">
+              <div class="flex-column justify-between">
+                <div class="flex justify-end align-center flex5 no-gutter">
+                  <div class="flex justify-end flex100">
+                    <div class="flex justify-end align-center">
+                      <input type="date" name="start-date" id="start-date" class="square-date" :class="{ 'date-placeholder': false }" data-placeholder="날짜 선택" v-model="search_date.value" />
+                      <span class="coupler">~</span>
+                      <input type="date" name="end-date" id="end-date" class="square-date ml5" :class="{ 'date-placeholder': false }" data-placeholder="날짜 선택" v-model="search_date.value2" />
+                      <select class="square-select ml5" v-model="search_data1.value">
+                        <option :value="null">공개여부</option>
+                        <option :value="true">공개</option>
+                        <option :value="false">비공개</option>
+                      </select>
+                      <select class="square-select ml5" v-model="search_data2.value">
+                        <option :value="null">카테고리</option>
+                        <option v-for="(item, indx) in categoryList" :key="indx" :value="item.cmmnCode"> {{ item.codeNm }} </option>
+                      </select>
+                      <select name="" id="searchItm1" class="square-select ml5" v-model="search_data3.key">
+                        <option :value="null">검색조건</option>
+                        <option value="post_sj">제목</option>
+                        <option value="post_dc">내용</option>
+                      </select>
+                      <div class="search-square ml5">
+                        <input type="text" class="square-input" placeholder="Search" v-model="search_data3.value" v-on:keyup.enter="searchData()" />
+                        <button class="square-button blue-btn" @click="searchData()">
+                          <svg-icon type="mdi" :path="this.$getIconPath()" class="square-icon"></svg-icon>
+                        </button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div class="table-zone flex85">
+                  <div class="list-info flex justify-between align-center">
+                    <div class="count-zone">
+                      <p>총 <span>40</span>건 중 <span>01</span>건 선택</p>
+                    </div>
+                    <div class="cunt-selectZone">
+                      <select name="" id="">
+                        <option value="">10개 보기</option>
+                        <option value="">20개 보기</option>
+                      </select>
+                    </div>
+                  </div>
+                  <table class="list-table">
+                    <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+                    <colgroup>
+                      <col style="width: 10%" />
+                      <col style="width: 30%" />
+                      <col style="width: 10%" />
+                      <col style="width: 10%" />
+                      <col style="width: 10%" />
+                      <col style="width: 30%" />
+                    </colgroup>
+                    <thead>
+                      <tr>
+                        <th>No</th>
+                        <th>제목</th>
+                        <th>생성자</th>
+                        <th>부서</th>
+                        <th>카테고리</th>
+                        <th>등록일시</th>
+                      </tr>
+                    </thead>
+                    <tbody>
+                      <template v-if="dataPostList.length > 0">
+                        <tr v-for="(item, indx) in dataPostList" :key="indx" @click="selectPost(item)">
+                          <td> {{ search.totalRows - indx - (search.currentPage - 1) * search.perPage }} </td>
+                          <td>{{ item.post_sj }}</td>
+                          <td>{{ item.creat_id }}</td>
+                          <td>{{ item.dept_code }}</td>
+                          <td>{{ item.ctgry_id }}</td>
+                          <td>{{ item.creat_dt }}</td>
+                        </tr>
+                      </template>
+                      <template v-else>
+                        <tr>
+                          <td colspan="6">등록된 데이터가 없습니다.</td>
+                        </tr>
+                      </template>
+                    </tbody>
+                  </table>
+                  <PaginationButton v-model:currentPage="search.currentPage" :perPage="search.perPage" :totalCount="search.totalRows" :maxRange="5" :click="searchData" />
+                </div>
+                <div>
+                  <label :for="comId + 'forder'" style="padding-left: 255px">
+                    <p class="width150 fl mt7" style="margin-bottom: 10px"> 데이터셋(ID) </p>
+                    <input type="text" :name="comId + 'forder'" class="input_M width50p fl" v-model="selectedDatasetId" readonly />
+                  </label>
+                </div>
+                <div class="flex5">
+                  <div class="flex justify-end">
+                    <button class="darkg-btn small-btn" @click="complite"> 완료 </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiClose } from "@mdi/js";
+import axios from "axios";
+import CodeList from "../../common/Component_CodeList.vue";
+import PaginationButton from "../../PaginationButton.vue";
+
+export default {
+  components: {
+    SvgIcon: SvgIcon,
+    CodeList: CodeList,
+    PaginationButton: PaginationButton,
+  },
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Boolean,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      isModalOpen: this.openPopup,
+      closePath: mdiClose,
+      jobItm: this.jobItem,
+
+      activeTab: "tab1",
+      modalType: "tab-modal",
+      //차트 Box ID (랜덤값 필수)
+      categoryList: [],
+      pagination: {},
+
+      selectedDatasetId: null, // 선택한 데이터 셋 아이디
+
+      // 데이터셋 목록
+      datasetPostList: [],
+      dataPostList: [],
+      search: this.$getDefaultSerchVO(),
+      // 공개여부
+      search_data1: this.$getDefaultSerchItem("public_at", "bool"),
+      // 카테고리
+      search_data2: this.$getDefaultSerchItem("ctgry_id", "string"),
+      // 선택
+      search_data3: this.$getDefaultSerchItem("post_sj", "string"),
+      // 날짜
+      search_date: this.$getDefaultSerchItem("creat_dt", "dates"),
+      categoryList: [],
+    };
+  },
+  mounted() {
+    this.init();
+  },
+  watch: {
+    openPopup(v) {
+      this.isModalOpen = v;
+    },
+    jobItem(v) {
+      this.jobItemInfo = v;
+    },
+  },
+  methods: {
+    async init() {
+      if (!this.jobItem) {
+        this.jobItm = Object.assign({}, this.$getDefaultJobGroup().node);
+        this.jobItm.itm = Object.assign(
+          {},
+          this.$getDefaultJobGroup().DatasetPost
+        );
+      } else {
+        this.selectedDatasetId = this.jobItm.itm.dataset_post_id;
+      }
+      this.search.searchObjectList.push(this.search_date);
+      this.search.searchObjectList.push(this.search_data1);
+      this.search.searchObjectList.push(this.search_data2);
+      this.search.searchObjectList.push(this.search_data3);
+      this.categoryList = await this.$getCommonCode("DATA_CTGRY");
+    },
+    // 탭 변경
+    showTab(tabName) {
+      this.activeTab = tabName;
+    },
+    // 카테고리 선택
+    categorySelect(item) {
+      this.pagination.categoryId = item.categoryId;
+      this.searchDatasetPostList();
+    },
+    //데이터셋 검색
+    searchDatasetPostList() {
+      this.pagination.currentPage = 1;
+      this.getDatasetPostList();
+    },
+    //데이터셋 데이터 리스트 타이틀 가져오기
+    getDatasetTitle(info) {
+      if (info.isStandard == true) {
+        return info.title;
+      } else {
+        return info.title + " (표준화필요)";
+      }
+    },
+    selectCode(code) {
+      this.search_data2.value = code;
+      this.searchData();
+    },
+    // 완료버튼 클릭
+    complite() {
+      const vm = this;
+      axios({
+        url: "/dataset/getDataTableInfo.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: { datapost_id: vm.selectedDatasetId },
+      })
+        .then(function (response) {
+          vm.jobItm.dataTable = response.data.resultData.dataTable;
+          vm.$emit("saveNodeData", vm.jobItm);
+        })
+        .catch(function (error) {
+          vm.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+
+    selectPost(item) {
+      this.jobItm.itm = item;
+      this.selectedDatasetId = item.dataset_post_id;
+    },
+    searchData() {
+      const vm = this;
+      axios({
+        url: "/dataset/selectDataPostList.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: JSON.stringify(vm.search),
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.success) {
+            vm.dataPostList = response.data.resultData.dataPostList;
+            vm.search.totalRows = response.data.resultData.totalRow;
+          }
+        })
+        .catch(function (error) {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+  },
+  computed: {
+    CodeList,
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/datasetUpdate.vue (added)
+++ client/views/component/connection/itm/datasetUpdate.vue
@@ -0,0 +1,439 @@
+<template>
+  <div v-show="isModalOpen" class="modal-wrapper">
+    <div class="modal-container large-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>데이터셋 업데이트</h2>
+          <button
+            type="button"
+            class="close-btn"
+            @click="$emit('fnCloseModal')"
+          >
+            <svg-icon
+              type="mdi"
+              :width="20"
+              :height="20"
+              :path="closePath"
+            ></svg-icon>
+          </button>
+        </div>
+      </div>
+      <div class="modal-content-monthly">
+        <div class="tab-contents mt10">
+          <div class="content-titleZone flex justify-between align-center">
+            <p class="box-title">데이터셋 설정</p>
+          </div>
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 15%" />
+              <col style="width: 85%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>데이터셋 설정</th>
+                <td>
+                  <button
+                    class="blue-border-btn small-btn"
+                    @click="fnTargetDataRead"
+                  >
+                    타겟 데이터 조회
+                  </button>
+                </td>
+              </tr>
+              <tr>
+                <th>기존 데이터 삭제 여부</th>
+                <td>
+                  <div class="input-container flex">
+                    <label class="radio-label mr5">
+                      <input
+                        type="radio"
+                        name="radio"
+                        :value="false"
+                        class="custom-radiobox"
+                        v-model="jobItm.itm_option_bool"
+                      />
+                      <span>기존 데이터 삭제</span>
+                    </label>
+                    <label class="radio-label">
+                      <input
+                        type="radio"
+                        name="radio"
+                        :value="true"
+                        class="custom-radiobox"
+                        v-model="jobItm.itm_option_bool"
+                      />
+                      <span>기존 데이터 미삭제</span>
+                    </label>
+                  </div>
+                </td>
+              </tr>
+            </tbody>
+          </table>
+          <div v-if="targetColumnDatas.length > 0" class="flex mt20">
+            <div class="flex50 pl0">
+              <div class="content-titleZone flex justify-between align-center">
+                <p class="box-title">
+                  컬럼 매칭
+                  <small>
+                    ( 매칭 : {{ matchs.matchCnt }}, 비매칭 :
+                    {{ matchs.unMatchCnt }}
+                    )
+                  </small>
+                </p>
+              </div>
+              <div
+                class="columncontBox"
+                style="height: 620px; overflow-y: auto"
+              >
+                <table>
+                  <colgroup>
+                    <col style="width: 50%" />
+                  </colgroup>
+                  <tbody>
+                    <tr v-for="(column, index) of columnMatchList" :key="index">
+                      <td>
+                        <p class="col-data">
+                          {{ column.columnOriginalName }}
+                        </p>
+                      </td>
+                      <td
+                        draggable="true"
+                        @dragstart="fnMoveStart(column)"
+                        @dragover="fnOnDragover"
+                        @drop="fnChangeMatchData('drop', index)"
+                        @dblclick="fnChangeMatchData('doubleClick', index)"
+                      >
+                        <p class="col-data">
+                          {{ column.columnMatchName }}
+                        </p>
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+            </div>
+            <div class="flex50 pr0">
+              <div class="content-titleZone flex justify-between align-center">
+                <p class="box-title">데이터 컬럼</p>
+              </div>
+              <div
+                class="columncontBox"
+                style="height: 620px; overflow-y: auto"
+              >
+                <template v-if="sourceColumnDatas.length > 0">
+                  <ul>
+                    <li
+                      v-for="(source, index) of sourceColumnDatas"
+                      :key="index"
+                      :class="{ 'col-data': true, disabled: !source.view }"
+                      :draggable="source.view"
+                      @dragstart="fnOnDragstart(source.displyColumnNm, index)"
+                    >
+                      {{ source.displyColumnNm }}
+                    </li>
+                  </ul>
+                </template>
+                <p v-else class="text-ct">등록된 데이터가 없습니다.</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button type="button" class="blue-btn small-btn" @click="fnSave">
+          저장
+        </button>
+        <button
+          type="button"
+          class="blue-border-btn small-btn"
+          @click="$emit('fnCloseModal')"
+        >
+          취소
+        </button>
+      </div>
+    </div>
+    <datasetSelecter
+      :openPopup="isSelectModalOpen"
+      @closePopup="fnCloseDatasetSelectModal"
+      @saveNodeData="fnUpdateDatasetSelect"
+    />
+  </div>
+</template>
+
+<script>
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiClose } from "@mdi/js";
+import axios from "axios";
+import datasetSelecter from "./datasetSelecter.vue";
+
+export default {
+  components: {
+    SvgIcon: SvgIcon,
+    datasetSelecter: datasetSelecter,
+  },
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Object,
+    },
+    nodes: {
+      type: Array,
+      required: true,
+    },
+    edges: {
+      type: Array,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      isModalOpen: this.openPopup,
+      closePath: mdiClose,
+      jobItm: this.jobItem,
+      nodeList: this.nodes,
+      edgeList: this.edges,
+
+      // 연결된 이전 노드
+      prevNode: {},
+
+      // 데이터셋 선택
+      isSelectModalOpen: false,
+
+      // 컬럼 매칭
+      columnMatchList: [],
+      sourceColumnDatas: [],
+      targetColumnDatas: [],
+      changeColList: [],
+      matchs: {
+        matchCnt: 0,
+        unMatchCnt: 0,
+      },
+      dragData: {
+        source: null,
+        sourceIdx: null,
+      },
+      datapostId: null,
+    };
+  },
+  mounted() {
+    this.init(); // 초기화
+    // 타겟 데이터가 있는 경우
+    if (this.jobItm.itm.hasOwnProperty("item")) {
+      if (this.jobItm.itm.item != null) {
+        this.datapostId = this.jobItm.itm.item;
+        this.fnSetupRead();
+      }
+    }
+  },
+  watch: {
+    columnMatchList: {
+      deep: true,
+      handler() {
+        this.fnMatchList(); // 매치 리스트
+      },
+    },
+  },
+  methods: {
+    // 초기화
+    init() {
+      // 연결된 노드 정보 조회(최초 1회만 실행)
+      for (let edge of this.edgeList) {
+        if (edge.target == this.jobItm.id) {
+          for (let node of this.nodeList) {
+            if (node.id == edge.source) {
+              this.prevNode = node;
+              this.fnPrevNodeDataRead(); // 노드 정보 조회 후, 컬럼 데이터 가져오기
+              return;
+            }
+          }
+        }
+      }
+      this.$showAlert(
+        "에러 발생",
+        "DATASET_UPDATE를 설정하기 전, 먼저 노드를 연결해 주세요."
+      );
+      this.$emit("fnCloseModal"); // 모달 닫기
+    },
+    // 소스 데이터 조회
+    fnPrevNodeDataRead() {
+      this.sourceColumnDatas = []; // 초기화
+      if (!this.$isEmpty(this.prevNode.dataTable)) {
+        let columnDatas = this.prevNode.dataTable.columnDatas;
+        for (let data of columnDatas) {
+          data.view = true;
+        }
+        this.sourceColumnDatas = columnDatas;
+      } else {
+        this.$showAlert(
+          "에러 발생",
+          "DATASET_UPDATE에 연결된 노드의 설정을 마쳐 주세요."
+        );
+      }
+    },
+    // 타겟 데이터 조회 후 처리
+    fnSetTargetData(columnDatas) {
+      // 타겟 컬럼의 ts_row는 조작 불가하므로 목록에서 제거
+      columnDatas = columnDatas.filter(
+        (column) => column.orginlColumnNm !== "ts_row"
+      );
+      this.targetColumnDatas = columnDatas; // 타겟 데이터 저장
+
+      this.columnMatchList = []; // 초기화
+      for (let i = 0; i < columnDatas.length; i++) {
+        this.columnMatchList.push({
+          targetIdx: i,
+          sourceIdx: null,
+          columnOriginalName: columnDatas[i].displyColumnNm,
+          columnMatchName: null,
+        });
+      }
+    },
+    // 타겟 데이터 조회
+    fnSetupRead() {
+      // 데이터 세팅
+      let data = {
+        datapost_id: this.datapostId,
+      };
+      // 실행
+      axios({
+        url: "/dataset/getDataTableInfo.json",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: data,
+      })
+        .then((response) => {
+          let columnDatas = response.data.resultData.dataTable.columnDatas;
+          this.fnSetTargetData(columnDatas); // 타겟 데이터 조회 후 처리
+        })
+        .catch((error) => {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+    // 타겟 데이터 조회
+    fnTargetDataRead() {
+      this.isSelectModalOpen = true;
+    },
+    // 타겟 데이터 조회 저장 후 모달 닫기
+    fnUpdateDatasetSelect(jobItem) {
+      this.datapostId = jobItem.itm.dataset_post_id;
+      this.fnSetTargetData(jobItem.dataTable.columnDatas); // 타겟 데이터 조회 후 처리
+      this.fnCloseDatasetSelectModal(); // 타겟 데이터 조회 모달 닫기
+    },
+    // 타겟 데이터 조회 모달 닫기
+    fnCloseDatasetSelectModal() {
+      this.isSelectModalOpen = false;
+    },
+    // 매치 리스트
+    fnMatchList() {
+      // 초기화
+      for (let sourceColumn of this.sourceColumnDatas) {
+        sourceColumn.view = true;
+      }
+      // 컬럼 매칭
+      let matchCnt = 0;
+      for (let col of this.columnMatchList) {
+        if (
+          !this.$isEmpty(col.sourceIdx) &&
+          !this.$isEmpty(col.columnMatchName)
+        ) {
+          matchCnt++; // 매칭 정보가 있는 경우 +1
+          this.sourceColumnDatas[col.sourceIdx].view = false; // 드래그 막기
+        }
+      }
+      // 컬럼 매칭 결과 조회
+      this.matchs.matchCnt = matchCnt;
+      this.matchs.unMatchCnt = this.targetColumnDatas.length - matchCnt;
+    },
+
+    // 드래그 이벤트
+    fnMoveStart(match) {
+      this.dragData.source = match.columnMatchName;
+      this.dragData.sourceIdx = match.sourceIdx;
+    },
+    fnOnDragstart(displyName, index) {
+      this.dragData.source = displyName;
+      this.dragData.sourceIdx = index;
+    },
+    fnOnDragover(event) {
+      event.preventDefault();
+    },
+    fnChangeMatchData(type, targetIdx) {
+      // 기존값 삭제
+      for (let match of this.columnMatchList) {
+        if (
+          match.sourceIdx == this.dragData.sourceIdx &&
+          match.columnMatchName == this.dragData.source
+        ) {
+          match.sourceIdx = null;
+          match.columnMatchName = null;
+        }
+      }
+      // 추가
+      for (let match of this.columnMatchList) {
+        if (match.targetIdx == targetIdx) {
+          if (type == "drop") {
+            match.sourceIdx = this.dragData.sourceIdx;
+            match.columnMatchName = this.dragData.source;
+          } else if (type == "doubleClick") {
+            match.sourceIdx = null;
+            match.columnMatchName = null;
+          }
+          break;
+        }
+      }
+    },
+
+    // 저장 버튼
+    fnSave() {
+      var itm = Object.assign({}, this.$getDefaultJobGroup().jobItemGroup);
+      itm.item = this.datapostId;
+      itm.itemList = []; // 초기화
+      for (let match of this.columnMatchList) {
+        itm.itemList.push({
+          orginColumnNm:
+            match.targetIdx != null && match.columnOriginalName != null
+              ? match.targetIdx + "/" + match.columnOriginalName
+              : null,
+          targetColumnNm:
+            match.sourceIdx != null && match.columnMatchName != null
+              ? match.sourceIdx + "/" + match.columnMatchName
+              : null,
+        });
+      }
+      this.jobItm.itm = itm;
+      this.$emit("fnSaveSetup", this.jobItm);
+    },
+  },
+};
+</script>
+
+<style scoped>
+.columncontBox {
+  background: #f5f5f5;
+  border-radius: 10px;
+}
+.columncontBox tr {
+  padding: 8px;
+}
+.columncontBox ul .col-data {
+  margin: 8px;
+}
+.columncontBox .col-data {
+  border: 1px solid #dbdbdb;
+  padding: 5px 10px;
+  background-color: var(--color-white);
+  border-radius: 10px;
+  cursor: move;
+  font-size: 1.3rem;
+  height: 34px;
+}
+.columncontBox .col-data.disabled {
+  background-color: #f5f5f5;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/connection/itm/fileDataRead.vue (added)
+++ client/views/component/connection/itm/fileDataRead.vue
@@ -0,0 +1,382 @@
+<template>
+  <div>
+    <div
+      v-show="!preview"
+      class="data_control flexCenter mt10"
+      style="display: flex; justify-content: space-evenly"
+    >
+      <div class="col-4 fl alignC">
+        <label for="datastart"> 데이터 시작 행 </label>
+        <div class="flexBox" style="align-items: center !important">
+          <input
+            type="text"
+            name="datastart"
+            v-model="rowIndex"
+            @blur="updateRowIndex"
+            @keyup.enter="updateRowIndex"
+          />
+          <div class="updownbox flexBox">
+            <input
+              type="button"
+              value="▲"
+              class="input_up"
+              @click="dataTableInfo.startRowIndex++"
+            />
+            <input
+              type="button"
+              value="▼"
+              class="input_down"
+              @click="dataTableInfo.startRowIndex--"
+            />
+          </div>
+        </div>
+      </div>
+      <div class="col-4 fl alignL">
+        <label for="dataend"> 데이터 시작 열 </label>
+        <div class="flexBox" style="align-items: center !important">
+          <input
+            type="text"
+            name="dataend"
+            v-model="cellIndex"
+            @blur="updateCellIndex"
+            @keyup.enter="updateCellIndex"
+          />
+          <div class="updownbox flexBox">
+            <input
+              type="button"
+              value="▲"
+              class="input_up"
+              @click="dataTableInfo.startCellIndex++"
+            />
+            <input
+              type="button"
+              value="▼"
+              class="input_down"
+              @click="dataTableInfo.startCellIndex--"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="datatableInfoBox" style="max-height: 550px; overflow: auto">
+      <div class="table-zone">
+        <!-- <div class="list-info flex justify-between align-center">
+                    <div class="count-zone">
+                        <p>총 <span>40</span>건 중 <span>01</span>건 선택</p>
+                    </div>
+                </div> -->
+        <table class="list-table">
+          <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+          <colgroup>
+            <col style="width: 10%" />
+            <col
+              v-for="(info, i) in dataTableInfo.changeColumnDatas"
+              :key="i"
+              :style="changeWidth()"
+            />
+          </colgroup>
+          <thead>
+            <tr>
+              <th>No</th>
+              <th
+                v-for="(info, i) in dataTableInfo.changeColumnDatas"
+                :key="i"
+                :style="
+                  i < dataTableInfo.startCellIndex
+                    ? 'background-color: #dfdfdf;'
+                    : null
+                "
+              >
+                [{{ i + 1 }}]
+              </th>
+            </tr>
+          </thead>
+          <!-- <tbody>
+                        <tr>
+                            <th :class="{ selected: getRowDataColumnIndex < 0 }">[1]</th>
+                            <td :class="{
+                            selected: getRowDataColumnIndex < 0
+                            , disabled: getStartRowIndex >= 0 || dataTableInfo.startCellIndex > j
+                        }"
+                                v-for="(info, j) in dataTableInfo.changeColumnDatas">
+                                {{ dataTableInfo.columnDatas[j].displyColumnNm }}
+                            </td>
+                        </tr>
+                        <tr v-for="(cells, i) in dataTableInfo.rowData" v-if="i < viewCount">
+                            <th :class="{ selected: getRowDataColumnIndex == i }">[{{ i + 2 }}]</th>
+                            <td :class="{
+                            selected: getRowDataColumnIndex == i
+                            , disabled: getStartRowIndex > i || dataTableInfo.startCellIndex > j
+                        }"
+                                v-for="(value, j) in cells" :title="value">
+                                {{ value }}
+                            </td>
+                        </tr>
+                        <tr v-if="postList === 0">
+                            <td colspan="5" class="no-list">검색조건에 해당하는 데이터가 없습니다.</td>
+                        </tr>
+                    </tbody> -->
+          <tbody>
+            <tr>
+              <th>[1]</th>
+              <td
+                v-for="(info, j) in dataTableInfo.changeColumnDatas"
+                :key="j"
+                :style="
+                  j < dataTableInfo.startCellIndex
+                    ? 'background-color: #dfdfdf;'
+                    : 'background-color: #fdd2d2;'
+                "
+              >
+                {{ dataTableInfo.columnDatas[j].displyColumnNm }}
+              </td>
+            </tr>
+            <tr
+              v-for="(cells, i) in dataTableInfo.rowData"
+              :key="i"
+              :style="
+                i + 1 < dataTableInfo.startRowIndex
+                  ? 'background-color: #dfdfdf;'
+                  : null
+              "
+            >
+              <th>[{{ i + 2 }}]</th>
+              <td
+                v-for="(value, j) in cells"
+                :key="j"
+                :title="value"
+                :style="
+                  j < dataTableInfo.startCellIndex
+                    ? 'background-color: #dfdfdf;'
+                    : null
+                "
+              >
+                {{ value }}
+              </td>
+            </tr>
+            <tr v-if="postList === 0">
+              <td colspan="5" class="no-list">
+                검색조건에 해당하는 데이터가 없습니다.
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<script>
+import _ from "lodash";
+
+export default {
+  name: "file-data-read",
+  props: {
+    isLoading: {
+      type: Boolean,
+      // default: true,
+    },
+    dataTable: {
+      type: Object,
+      default: {},
+    },
+    // 미리보기, 데이터셋 설정 화면 구분
+    preview: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      dataTableInfo: this.dataTable,
+      columnIndex: this.dataTable.rowDataColumnIndex,
+      rowIndex: this.dataTable.startRowIndex + 1,
+      cellIndex: this.dataTable.startCellIndex + 1,
+    };
+  },
+  methods: {
+    // 데이터 확정후 창닫기
+    displayIndex: function (index) {
+      if (index) {
+        return index + 1;
+      } else {
+        return 1;
+      }
+    },
+
+    // 데이터 테이블 넓이 조정
+    changeWidth: function () {
+      return `width: ${90 / this.dataTableInfo.changeColumnDatas.length}%`;
+    },
+
+    // 데이터 시작 행 변경
+    updateRowIndex() {
+      this.dataTableInfo.startRowIndex = this.rowIndex - 1; // 엔터나 입력창 바깥 클릭 시 값 업데이트
+    },
+
+    // 데이터 시작 열 변경
+    updateCellIndex() {
+      this.dataTableInfo.startCellIndex = this.cellIndex - 1; // 엔터나 입력창 바깥 클릭 시 값 업데이트
+    },
+  },
+  watch: {
+    dataTable: function (v) {
+      //데이터 테이블 정보 변경 시(다른 데이터 셋 선택시)
+      this.dataTableInfo = _.cloneDeep(v);
+      // 데이터 테이블 변경 이력이 없을 시 초기 세팅
+      if (!v.columnNameChange) {
+        for (let i = 0; i < this.dataTableInfo.columnDatas.length; i++) {
+          // this.dataTableInfo.changeColumnDatas[i].columnNm = "col"+i;//*columnNm = 저장될 컬럼명
+          this.dataTableInfo.columnDatas[i].columnNm = "col" + i; //*columnNm = 저장될 컬럼명
+
+          //*displyColumnNm
+          if (this.dataTableInfo.columnDatas[i].orginlColumnNm) {
+            //원본 cell의 value가 빈값이 아닐 때
+            //displyColumnNm = 원본 cell의 value
+            // this.dataTableInfo.changeColumnDatas[i].displyColumnNm = this.dataTableInfo.columnDatas[i].orginlColumnNm;
+            this.dataTableInfo.columnDatas[i].displyColumnNm =
+              this.dataTableInfo.columnDatas[i].orginlColumnNm;
+          } else {
+            //원본 cell의 value가 빈값일 때
+            //displyColumnNm = columnNm(DB에 생성될 컬럼명)
+            // this.dataTableInfo.changeColumnDatas[i].displyColumnNm = "col"+i;
+            this.dataTableInfo.columnDatas[i].displyColumnNm = "col" + i;
+          }
+        }
+        this.dataTableInfo.originalColumnDatas = _.cloneDeep(
+          this.dataTableInfo.columnDatas
+        ); // 원본 컬럼행 데이터 저장
+        this.dataTableInfo.changeColumnDatas = _.cloneDeep(
+          this.dataTableInfo.columnDatas
+        ); // 변경된 컬럼행 데이터 저장 (원본도 변경하는데 왜 필요한거지?)
+      }
+      this.$emit("update:isLoading", false); // 데이터테이블 복사 완료 시 로딩 해제
+    },
+
+    //데이터 시작 행의 Index
+    "dataTableInfo.startRowIndex": function (value) {
+      this.rowIndex = value + 1;
+      //value(Index)값이 숫자가 아닐 때 => 0
+      try {
+        if (typeof parseInt(value) !== "number") {
+          this.dataTableInfo.startRowIndex = 0;
+          this.rowIndex = this.dataTableInfo.startRowIndex + 1;
+          return;
+        }
+      } catch (e) {
+        this.dataTableInfo.startRowIndex = 0;
+        this.rowIndex = this.dataTableInfo.startRowIndex + 1;
+        return;
+      }
+
+      let maxIndex = this.dataTableInfo.rowData.length; //컬럼(필드)행의 최대 Index = 데이터의 행 수 - 1
+
+      if (value < 0) {
+        //value(Index)가 데이터 시작 행의 Index보다 작을 때
+        this.dataTableInfo.startRowIndex = 0;
+        this.rowIndex = this.dataTableInfo.startRowIndex + 1;
+
+        this.dataTableInfo.rowDataColumnIndex = 0;
+      } else if (value > maxIndex) {
+        //value(Index)가 최대 Index 보다 클 때
+        this.dataTableInfo.startRowIndex = maxIndex;
+        this.rowIndex = maxIndex + 1;
+
+        this.dataTableInfo.rowDataColumnIndex = maxIndex;
+      } else {
+        //정상적으로 바뀔 때
+        this.dataTableInfo.changeColumnDatas = _.cloneDeep(
+          this.dataTableInfo.columnDatas
+        );
+
+        if (value > 0) {
+          //원본 컬럼으로 바뀌는게 아닐 때
+          //rowData에서 row정보를 받아옴
+          let row = this.dataTableInfo.rowData[value - 1];
+          for (
+            let i = 0;
+            i < this.dataTableInfo.changeColumnDatas.length;
+            i++
+          ) {
+            this.dataTableInfo.changeColumnDatas[i].orginlColumnNm = row[i]; //*orginlColumnNm = 원본 cell의 value
+            this.dataTableInfo.changeColumnDatas[i].columnNm = "col" + i; //*columnNm = 저장될 컬럼명
+            this.dataTableInfo.columnDatas[i].orginlColumnNm = row[i]; //*orginlColumnNm = 원본 cell의 value
+            this.dataTableInfo.columnDatas[i].columnNm = "col" + i; //*columnNm = 저장될 컬럼명
+
+            //*displyColumnNm
+            if (row[i]) {
+              //원본 cell의 value가 빈값이 아닐 때
+              //displyColumnNm = 원본 cell의 value
+              this.dataTableInfo.changeColumnDatas[i].displyColumnNm = row[i];
+              this.dataTableInfo.columnDatas[i].displyColumnNm = row[i];
+            } else {
+              //원본 cell의 value가 빈값일 때
+              //displyColumnNm = columnNm(DB에 생성될 컬럼명)
+              this.dataTableInfo.changeColumnDatas[i].displyColumnNm =
+                "col" + i;
+              this.dataTableInfo.columnDatas[i].displyColumnNm = "col" + i;
+            }
+          }
+        }
+        //★ 메모상에서 컬럼 데이터 변경 여부
+        this.dataTableInfo.columnNameChange = true;
+      }
+      this.dataTableInfo.rowDataColumnIndex = this.dataTableInfo.startRowIndex;
+
+      // 데이터 시작 행이 0일 때, 초기 컬럼 데이터로 변경
+      if (this.dataTableInfo.startRowIndex == 0) {
+        this.dataTableInfo.columnDatas = _.cloneDeep(
+          this.dataTableInfo.originalColumnDatas
+        );
+      }
+
+      this.$emit("index-change", this.dataTableInfo);
+    },
+
+    //데이터 시작 열의 Index
+    "dataTableInfo.startCellIndex": function (value) {
+      this.cellIndex = value + 1;
+      //value(Index)값이 숫자가 아닐 때 => 0
+      try {
+        if (typeof parseInt(value) !== "number") {
+          this.dataTableInfo.startCellIndex = 0;
+          this.cellIndex = this.dataTableInfo.startCellIndex + 1;
+          return;
+        }
+      } catch (e) {
+        this.dataTableInfo.startCellIndex = 0;
+        this.cellIndex = this.dataTableInfo.startCellIndex + 1;
+        return;
+      }
+
+      //데이터 시작 열 최대 Index = 데이터의 컬럼 갯수
+      let maxIndex = this.dataTableInfo.columnDatas.length - 1;
+      if (value < 0) {
+        //value(Index)가 0보다 작을 때
+        this.dataTableInfo.startCellIndex = 0;
+        this.cellIndex = this.dataTableInfo.startCellIndex + 1;
+        return;
+      } else if (value > maxIndex) {
+        //value(Index)가 최대 Index 보다 클 때
+        this.dataTableInfo.startCellIndex = maxIndex;
+        this.cellIndex = maxIndex + 1;
+        return;
+      }
+      this.$emit("index-change", this.dataTableInfo);
+    },
+  },
+  computed: {
+    //column Index에 의한 컬럼 사용 타입 변경  => Index 미만 컬럼은 사용 불가
+    columnUseChange: function (columnDatas, index) {
+      for (let i = 0; i < columnDatas.length; i++) {
+        if (i > index - 1) {
+          columnDatas[i].useAt = true;
+        } else {
+          columnDatas[i].useAt = false;
+        }
+      }
+    },
+  },
+};
+</script>
 
client/views/component/connection/itm/fileSelect.vue (added)
+++ client/views/component/connection/itm/fileSelect.vue
@@ -0,0 +1,1029 @@
+<template>
+  <div
+    v-show="isModalOpen"
+    class="modal-wrapper"
+    :style="isLoading ? { cursor: 'wait' } : {}"
+  >
+    <div v-if="isLoading" class="loading-overlay">
+      <div class="loading-div">
+        <span>LOADING&nbsp;</span>
+        <span class="anima">.</span>
+        <span class="anima">.</span>
+        <span class="anima">.</span>
+      </div>
+    </div>
+    <div class="modal-container list-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2 v-show="modeInfo == 'fileRead'">파일 읽기</h2>
+          <h2 v-show="modeInfo == 'fileWrite'">파일 쓰기</h2>
+          <button class="close-btn" @click="cancelModal()">X</button>
+        </div>
+      </div>
+      <div class="modal-content-monthly" v-show="currentPage == 1">
+        <div class="content-wrap">
+          <div class="content-box flex justify-between">
+            <div class="flex20 content-box">
+              <div class="left-content flex100 content-box">
+                <div class="content-box">
+                  <div class="mb10">
+                    <div class="content-titleZone">
+                      <p class="box-title">호스트 선택</p>
+                    </div>
+                    <div class="flex align-center justify-between no-gutter">
+                      <div class="flex100">
+                        <select
+                          name=""
+                          id=""
+                          class="only full-select"
+                          v-model="selectedHostCode"
+                        >
+                          <option :value="null" disabled>선택</option>
+                          <option
+                            v-for="(host, idx) in hostList"
+                            :key="idx"
+                            :value="host.host_code"
+                          >
+                            {{ host.host_nm + " - (" + host.host_ip + ")" }}
+                          </option>
+                        </select>
+                      </div>
+                      <div class="flex100">
+                        <button
+                          class="blue-border-btn large-btn"
+                          @click="connectionConfirm()"
+                        >
+                          연결
+                        </button>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="file-tree-zone">
+                    <div
+                      class="content-titleZone flex justify-between align-center"
+                    >
+                      <p class="box-title">폴더 리스트</p>
+                    </div>
+                    <div class="file-zone overflow-y">
+                      <ul class="tree-wrap">
+                        <TreeItem
+                          :connection="connection"
+                          :selectedNode="selectedNode"
+                          :selectItem="currentItem"
+                          v-for="(item, idx) in nodes"
+                          :item="item"
+                          :idx="item.id"
+                          :key="idx"
+                          @selectFolder="selectFolder"
+                          @isLoading="handleIsLoading"
+                          @selectItem="handleSelectItem"
+                        />
+                      </ul>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="flex80 content-box">
+              <div class="right-content flex100">
+                <div class="flex-column justify-between">
+                  <div class="flex justify-between align-center no-gutter">
+                    <div class="flex justify-end flex100">
+                      <div class="search-bar">
+                        <div class="flex justify-end align-center">
+                          <select
+                            name=""
+                            id=""
+                            class="square-select"
+                            v-model="searchType"
+                          >
+                            <option value="all">전체</option>
+                            <option value="folder">현재폴더</option>
+                          </select>
+                          <div class="search-square">
+                            <div
+                              class="flex justify-end align-center no-gutter"
+                            >
+                              <input
+                                type="text"
+                                class="square-input flex90"
+                                placeholder="Search"
+                                v-model="searchText"
+                                @keyup.enter="searchFiles()"
+                              />
+                              <button class="square-button blue-btn flex10">
+                                <svg-icon
+                                  type="mdi"
+                                  :path="searchPath"
+                                  class="square-icon"
+                                  @click="searchFiles()"
+                                ></svg-icon>
+                              </button>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="content-zone" style="overflow-y: auto">
+                    <div class="table-zone file-table">
+                      <div class="list-info flex justify-between align-center">
+                        <div class="count-zone">
+                          <p>
+                            총
+                            <span v-if="search.totalRows != 0">{{
+                              search.totalRows
+                            }}</span>
+                            <span v-else>0</span>
+                            건
+                          </p>
+                          <p>
+                            현재경로 : <span>{{ connection.path }}</span>
+                          </p>
+                        </div>
+                      </div>
+                      <table class="list-table">
+                        <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+                        <colgroup>
+                          <col style="width: 5%" />
+                          <col style="width: 45%" />
+                          <col style="width: 10%" />
+                          <col style="width: 20%" />
+                          <col style="width: 10%" />
+                          <col style="width: 10%" />
+                        </colgroup>
+                        <thead>
+                          <tr>
+                            <th></th>
+                            <th>이름</th>
+                            <th>타입</th>
+                            <th>마지막 수정</th>
+                            <th>크기</th>
+                            <th>관리</th>
+                          </tr>
+                        </thead>
+                        <tbody>
+                          <tr
+                            v-for="(file, index) in fileList"
+                            :key="index"
+                            @click="selectFileList(file)"
+                          >
+                            <td
+                              v-if="file.text != '상위폴더로 이동'"
+                              @click.stop=""
+                            ></td>
+                            <td v-else @click.stop=""></td>
+                            <td>
+                              <div class="text-lf">
+                                <span v-if="file.extension === 'txt'"
+                                  ><img
+                                    src="../../../../resources/img/icon/txt.png"
+                                    ref=""
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'pdf'"
+                                  ><img
+                                    src="../../../../resources/img/icon/pdf.png"
+                                    alt=""
+                                /></span>
+                                <span
+                                  v-else-if="
+                                    file.extension === 'pptx' ||
+                                    file.extension === 'ppt'
+                                  "
+                                  ><img
+                                    src="../../../../resources/img/icon/ppt.png"
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'hwp'"
+                                  ><img
+                                    src="../../../../resources/img/icon/hwp.png"
+                                    alt=""
+                                /></span>
+                                <span
+                                  v-else-if="
+                                    file.extension === 'xls' ||
+                                    file.extension === 'xlsx'
+                                  "
+                                  ><img
+                                    src="../../../../resources/img/icon/xls.png"
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'jpg'"
+                                  ><img
+                                    src="../../../../resources/img/icon/img.png"
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'png'"
+                                  ><img
+                                    src="../../../../resources/img/icon/img.png"
+                                    ref=""
+                                    alt=""
+                                /></span>
+                                <span v-else>&emsp;&ensp;</span>
+                                <span>&ensp;{{ file.text }}</span>
+                              </div>
+                            </td>
+                            <td>{{ file.extension }}</td>
+                            <td>{{ file.lastUpdate }}</td>
+                            <td v-if="file.size != 0">
+                              {{ $filters.bytesToSize(file.size) }}
+                            </td>
+                            <td v-else></td>
+                            <td>
+                              <div
+                                v-if="!file.folder && modeInfo == 'fileWrite'"
+                              >
+                                <button
+                                  class="icon-btn orange-btn"
+                                  title="미리보기"
+                                  @click.stop="filePreview(file)"
+                                >
+                                  <svg-icon
+                                    type="mdi"
+                                    :width="18"
+                                    :height="18"
+                                    :path="fileFindPath"
+                                  ></svg-icon>
+                                </button>
+                              </div>
+                              <div v-else></div>
+                            </td>
+                          </tr>
+                        </tbody>
+                      </table>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div v-if="modeInfo == 'fileRead'" class="table-zone">
+          <table class="form-table">
+            <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+            <colgroup>
+              <col style="width: 20%" />
+              <col style="width: 80%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>폴더명</th>
+                <td>{{ selectedFile["parent"] }}</td>
+              </tr>
+              <tr>
+                <th>파일명</th>
+                <td>{{ selectedFile["text"] }}</td>
+              </tr>
+            </tbody>
+          </table>
+          <div class="flex flex30 align-center justify-center">
+            <div class="input-container flex">
+              <label class="check-label">
+                <input type="checkbox" name="check" class="custom-checkbox" />
+                <span>마지막 파일만</span>
+              </label>
+              <label class="check-label">
+                <input type="checkbox" name="check" class="custom-checkbox" />
+                <span>업데이트일 이후</span>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div v-else-if="modeInfo == 'fileWrite'" class="table-zone">
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 10%" />
+              <col style="width: 40%" />
+              <col style="width: 10%" />
+              <col style="width: 40%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>폴더명</th>
+                <td>{{ selectedFile["parent"] }}</td>
+                <th>쓰기유형</th>
+                <td>
+                  <select name="" id="" class="full-select">
+                    <option selected>엑셀</option>
+                  </select>
+                </td>
+              </tr>
+              <tr>
+                <th>파일명</th>
+                <td>
+                  <input
+                    type="text"
+                    name=""
+                    id=""
+                    class="full-input"
+                    v-model="fileNameBeforeDot"
+                  />
+                </td>
+                <th>쓰기옵션</th>
+                <td>
+                  <div class="flex flex30 align-center justify-center">
+                    <div class="input-container flex">
+                      <label class="radio-label">
+                        <input
+                          type="radio"
+                          name="radio"
+                          checked=""
+                          class="custom-radiobox"
+                          v-model="selectedFile['streOptn']"
+                          :value="false"
+                        />
+                        <span>쓰기</span>
+                      </label>
+                      <label class="radio-label">
+                        <input
+                          type="radio"
+                          name="radio"
+                          class="custom-radiobox"
+                          v-model="selectedFile['streOptn']"
+                          :value="true"
+                        />
+                        <span>suffix</span>
+                      </label>
+                    </div>
+                  </div>
+                </td>
+              </tr>
+              <tr v-show="selectedFile['streOptn']">
+                <th>suffix</th>
+                <td>
+                  <input
+                    type="text"
+                    name=""
+                    id=""
+                    class="full-input"
+                    v-model="selectedFile['suffix']"
+                  />
+                </td>
+                <th>월변경</th>
+                <td>
+                  <input
+                    type="number"
+                    name=""
+                    id=""
+                    class="full-input"
+                    v-model="selectedFile['addMonth']"
+                  />
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <div class="modal-content-monthly" v-show="currentPage == 2">
+        <div class="content-titleZone">
+          <p v-show="modeInfo == 'fileRead'" class="box-title">데이터셋 설정</p>
+          <p v-show="modeInfo == 'fileWrite'" class="box-title">
+            데이터 미리보기
+          </p>
+        </div>
+        <FileDataRead
+          v-if="isModalOpen"
+          v-model:isLoading="isLoading"
+          v-model:dataTable="dataTable"
+          :preview="preview"
+          @indexChange="indexChange"
+        ></FileDataRead>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button
+          class="blue-btn small-btn"
+          @click="setPage(1)"
+          v-show="currentPage == 2"
+        >
+          이전
+        </button>
+        <button
+          class="blue-btn small-btn"
+          @click="setPage(2)"
+          v-show="currentPage == 1 && modeInfo == 'fileRead'"
+        >
+          다음
+        </button>
+        <button
+          class="blue-btn small-btn"
+          @click="saveModal()"
+          v-show="
+            (currentPage == 2 && modeInfo == 'fileRead') ||
+            (currentPage == 1 && modeInfo == 'fileWrite')
+          "
+        >
+          완료
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<script>
+import axios from "axios";
+import TreeItem from "../../FileTree.vue";
+import SvgIcon from "@jamescoyle/vue-icon";
+import FileDataRead from "./fileDataRead.vue";
+import {
+  mdiMagnify,
+  mdiClose,
+  mdiFileCog,
+  mdiDownload,
+  mdiFileFind,
+  mdiPlay,
+  mdiCancel,
+} from "@mdi/js";
+import store from "../../../pages/AppStore";
+import _ from "lodash";
+
+export default {
+  name: "database-connection",
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Boolean,
+      default: null,
+    },
+    mode: {
+      type: String,
+      default: null, // 읽기: fileRead, 쓰기: fileWrite
+    },
+  },
+  data() {
+    return {
+      // 로딩 상태
+      isLoading: false,
+
+      // 초기 접속 확인
+      isInit: false,
+
+      // 현재 모달창 관리
+      isModalOpen: this.openPopup,
+      jobItm: this.jobItem,
+      modeInfo: this.mode,
+
+      currentPage: 1,
+      searchPath: mdiMagnify, // 검색 아이콘 경로
+      fileFindPath: mdiFileFind, // 미리보기 아이콘 경로
+
+      // 검색 객체
+      search: this.$getDefaultSerchVO(),
+      search_data: this.$getDefaultSerchItem(null, "String"),
+
+      selectedHost: {},
+      selectedHostCode: null,
+      // 호스트 목록
+      hostList: [],
+      // 부서-호스트 목록
+      deptHostList: [],
+      connection: {
+        host_code: null,
+        path: null,
+        depth: null,
+        type: null,
+      },
+      nodes: [],
+      selectedNode: null,
+      fileList: [],
+
+      searchType: "all",
+      searchText: null,
+
+      selectItem: null,
+      currentItem: {
+        id: null,
+      },
+
+      selectedFile: {},
+
+      // 초기 빈 데이터 테이블
+      defaultDataTable: {},
+
+      // 임시 저장된 데이터
+      tempJobItm: this.jobItem,
+      tempCurrentPage: 1,
+      tempSearch: this.$getDefaultSerchVO(),
+      tempSearch_data: this.$getDefaultSerchItem(null, "String"),
+      tempSearchText: null,
+      tempConnection: {
+        host_code: null,
+        path: null,
+        depth: null,
+        type: null,
+      },
+      tempNodes: [],
+      tempSelectedNode: null,
+      tempFileList: [],
+      tempSelectedFile: {},
+      tempSelectedHostCode: null,
+      tempDataTable: {},
+      /** ************************************ 데이터 셋 읽기 ************************************ */
+      dataTable: {},
+      preview: false,
+
+      fileNameBeforeDot: null, // 파일명(확장자 제외)
+    };
+  },
+  methods: {
+    // 모달 열기
+    openModal: function () {
+      if (!this.isInit) {
+        // 데이터 초기화
+        this.selectHosts();
+        this.searchInit();
+        /* this.jobItm = _.cloneDeep(this.tempJobItm);
+              this.currentPage = 1;
+              this.search = this.$getDefaultSerchVO(),
+              this.search_data = this.$getDefaultSerchItem(null, 'String'),
+              this.searchText = null;
+              this.connection = {};
+              this.nodes = [];
+              this.selectedNode = null;
+              this.fileList = [];
+              this.selectedFile = {};
+              this.selectedHostCode = null;
+              this.dataTable = {}; */
+      }
+      // 데이터를 임시 저장 데이터에 복사
+      this.tempJobItm = _.cloneDeep(this.jobItm);
+      this.tempCurrentPage = _.cloneDeep(this.currentPage);
+      this.tempSearch = _.cloneDeep(this.search);
+      this.tempSearch_data = _.cloneDeep(this.search_data);
+      this.tempSearchText = _.cloneDeep(this.searchText);
+      this.tempConnection = _.cloneDeep(this.connection);
+      this.tempNodes = _.cloneDeep(this.nodes);
+      this.tempSelectedNode = _.cloneDeep(this.selectedNode);
+      this.tempFileList = _.cloneDeep(this.fileList);
+      this.tempSelectedFil = _.cloneDeep(this.selectedFile);
+      this.tempSelectedHostCode = _.cloneDeep(this.selectedHostCode);
+      this.tempDataTable = _.cloneDeep(this.dataTable);
+
+      this.isModalOpen = true;
+    },
+
+    // 모달 닫기
+    closeModal: function () {
+      this.isModalOpen = false;
+      if (this.jobItem) {
+        this.$emit("closePopup", this.jobItem.id, this.dataTable);
+      }
+
+      this.$emit("closePopup2", this.jobItm.indx, this.jobItm, this.connection);
+    },
+
+    // 화면전환
+    async setPage(val) {
+      if (val == 2) {
+        if (!this.selectedFile["text"]) {
+          this.$showAlert("파일 읽기", "파일을 선택해주세요.");
+          return;
+        }
+        await this.fileRead();
+        this.currentPage = val;
+      } else {
+        this.currentPage = val;
+      }
+    },
+
+    // 모달 취소
+    cancelModal: function () {
+      // 데이터를 임시 저장 데이터로 복구
+      this.jobItm = this.tempJobItm;
+      this.currentPage = this.tempCurrentPage;
+      this.search = this.tempSearch;
+      this.search_data = this.tempSearch_data;
+      this.searchText = this.tempSearchText;
+      this.connection = this.tempConnection;
+      this.nodes = this.tempNodes;
+      this.selectedNode = this.tempSelectedNode;
+      this.fileList = this.tempFileList;
+      this.selectedFile = this.tempSelectedFil;
+      this.selectedHostCode = this.tempSelectedHostCode;
+      this.dataTable = this.tempDataTable;
+
+      this.closeModal();
+    },
+
+    // 모달 저장
+    async saveModal() {
+      this.isInit = true; // 한 번이라도 저장 시 모달 초기화 안 함
+
+      // 파일 쓰기용 데이터를 jobItm에 저장
+      if (this.modeInfo == "fileWrite") {
+        if (
+          !this.selectedFile["streOptn"] &&
+          this.$filters.removeExtension(this.selectedFile["text"]) ==
+            this.fileNameBeforeDot
+        ) {
+          if (
+            !(await this.$showConfirm(
+              "파일 쓰기",
+              "파일명이 동일할 시 덮어쓰기 됩니다."
+            ))
+          ) {
+            return;
+          }
+        }
+
+        this.jobItm.itm["path"] = this.selectedFile["parent"];
+        this.jobItm.itm["fileName"] =
+          this.fileNameBeforeDot + "." + this.selectedFile["fileFom"];
+        this.jobItm.itm["fileFom"] = this.selectedFile["fileFom"];
+        this.jobItm.itm["streOptn"] = this.selectedFile["streOptn"];
+        this.jobItm.itm["suffix"] = this.selectedFile["suffix"];
+        this.jobItm.itm["addMonth"] = this.selectedFile["addMonth"];
+
+        this.jobItm.dataTable = _.cloneDeep(this.defaultDataTable);
+      }
+      this.closeModal();
+    },
+
+    // 검색 객체 초기화
+    searchInit: function () {
+      this.search.searchObjectList.push(this.search_data);
+    },
+
+    // 파일 검색
+    searchFiles() {
+      const vm = this;
+
+      if (vm.searchText === null || vm.searchText === "") {
+        vm.$showAlert("파일 검색", "검색어를 입력해 주세요.");
+        return;
+      }
+
+      let path = vm.connection.path;
+
+      if (vm.searchType === "all") {
+        path = "#";
+      }
+      vm.isLoading = true; // 로딩 시작
+
+      axios({
+        url: "/files/search/" + vm.connection.host_code,
+        method: "post",
+        headers: {},
+        data: {
+          searchType: vm.searchType,
+          searchText: vm.searchText,
+          path: path,
+        },
+      })
+        .then(function (response) {
+          vm.isLoading = false; // 로딩 해제
+          vm.fileList = response.data.resultData.fileList;
+          if (vm.fileList.length == 0) {
+            vm.$showAlert("파일 검색", "검색 결과가 없습니다.");
+          }
+        })
+        .catch(function (error) {
+          vm.isLoading = false; // 로딩 해제
+          vm.$showAlert(
+            "파일 검색",
+            "파일 검색 오류, 관리자에게 문의바랍니다."
+          );
+        });
+    },
+
+    // 로딩 상태 변경
+    handleIsLoading(isLoadingValue) {
+      this.isLoading = isLoadingValue; // 부모 컴포넌트의 데이터 업데이트
+    },
+
+    // 호스트 목록 조회
+    selectHosts() {
+      const vm = this;
+      axios({
+        url: "/files/hosts",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: {
+          userId: store.state.loginUser.user_id,
+          authList: store.state.loginUser.user_auth,
+          author: store.state.loginUser.user_auth[0],
+          dept_code: store.state.loginUser.dept_code,
+        },
+      })
+        .then((response) => {
+          vm.hostList = response.data.resultData.hostList;
+          vm.deptHostList = response.data.resultData.deptHostList;
+          if (this.hostList.length > 0) {
+            vm.selectedHost = vm.hostList[0];
+            vm.selectedHostCode = vm.hostList[0].host_code;
+            for (let i = 0; i < vm.hostList.length; i++) {
+              for (let j = 0; j < vm.deptHostList.length; j++) {
+                if (vm.hostList[i].host_code === vm.deptHostList[j].host_code) {
+                  vm.hostList[i].main_folder_path =
+                    vm.deptHostList[j].main_folder_path;
+                }
+              }
+            }
+            vm.connectionConfirm();
+          } else {
+            vm.selectedHost = {};
+          }
+        })
+        .catch((error) => {
+          vm.$showAlert(
+            "호스트 조회",
+            "호스트 조회 오류, 관리자에게 문의하세요."
+          );
+        });
+    },
+
+    // 선택한 호스트 연결
+    async connectionConfirm() {
+      const vm = this;
+      if (vm.selectedHostCode === null || vm.selectedHostCode === undefined) {
+        vm.$showAlert("파일시스템 연결", "호스트를 선택해 주세요.");
+        return;
+      }
+
+      // 이전에 선택한 호스트 코드가 있으면 저장
+      let tempHostCode = null;
+      if (vm.connection.host_code) {
+        tempHostCode = _.cloneDeep(vm.connection.host_code);
+      }
+
+      vm.connection.host_code = vm.selectedHost.host_code;
+
+      vm.isLoading = true; // 로딩 시작
+
+      /* axios.get('/files/connection', { params: { 'host_code': vm.connection.host_code } })
+              .then(response => {
+                  vm.$showAlert('파일시스템 연결', response.data.message);
+                  if (response.data.status === 200) {
+                      vm.fileTreeList();
+                  }
+                  vm.selectedFile = {};
+              })
+              .catch(error => {
+                  vm.$showAlert('파일시스템 연결', '파일시스템 연결 오류, 관리자에게 문의하세요.');
+                  vm.selectedFile = {};
+              }); */
+
+      try {
+        const response = await axios.get("/files/connection", {
+          params: { host_code: vm.connection.host_code },
+        });
+
+        vm.isLoading = false; // 로딩 해제
+        if (response.data.status === 200) {
+          await this.fileTreeList();
+        } else {
+          // 이전에 선택한 호스트 코드로 변경
+          if (tempHostCode) {
+            vm.connection.host_code = tempHostCode;
+            vm.selectedHost.host_code = tempHostCode;
+            vm.selectedHostCode = tempHostCode;
+          }
+        }
+        vm.selectedFile = {};
+        vm.$showAlert("파일시스템 연결", response.data.message);
+      } catch (error) {
+        vm.isLoading = false; // 로딩 해제
+        vm.$showAlert(
+          "파일시스템 연결",
+          "파일시스템 연결 오류, 관리자에게 문의하세요."
+        );
+        vm.selectedFile = {};
+        // 이전에 선택한 호스트 코드로 변경
+        if (tempHostCode) {
+          vm.connection.host_code = tempHostCode;
+          vm.selectedHost.host_code = tempHostCode;
+          vm.selectedHostCode = tempHostCode;
+        }
+      }
+    },
+
+    // 폴더 리스트 조회
+    async fileTreeList() {
+      const vm = this;
+      vm.nodes = [];
+      vm.connection.path = vm.selectedHost.main_folder_path;
+      vm.connection.depth = 0;
+      vm.connection.type = "folder";
+
+      vm.isLoading = true; // 로딩 시작
+
+      try {
+        const response = await axios.get("/files/tree", {
+          params: vm.connection,
+        });
+
+        vm.nodes = response.data.resultData.fileTree;
+        // vm.connection.path = null;
+        vm.fileList = [];
+        vm.isLoading = false; // 로딩 해제
+      } catch (error) {
+        vm.$showAlert(
+          "파일리스트 조회",
+          "파일리스트 조회 오류, 관리자에게 문의하세요."
+        );
+        // vm.connection.path = null;
+        vm.fileList = [];
+        vm.isLoading = false; // 로딩 해제
+        vm.$showAlert(
+          "파일리스트 조회",
+          "파일리스트 조회 오류, 관리자에게 문의하세요."
+        );
+      }
+    },
+
+    // 폴더 선택
+    selectFolder(path) {
+      if (path === null || path === undefined) {
+        this.isLoading = false;
+      }
+      this.connection.path = path;
+      this.fileSelectList();
+    },
+
+    handleSelectItem(item, parentItem) {
+      // 현재 선택된 폴더와 클릭한 폴더가 같을 때
+      if (this.currentItem.id == item.id) {
+        this.selectItem = parentItem;
+        this.currentItem = parentItem;
+      } else {
+        this.selectItem = item;
+        this.currentItem = item;
+      }
+    },
+
+    // 파일 리스트 조회
+    fileSelectList() {
+      const vm = this;
+
+      if (vm.connection.path === null || vm.connection.path === "") {
+        vm.fileList = null;
+        return;
+      }
+
+      vm.connection.type = "all";
+      vm.connection.depth = 1;
+
+      axios
+        .get("/files/list", { params: vm.connection })
+        .then((response) => {
+          vm.fileList = response.data.resultData.fileList;
+          vm.search.totalRows = response.data.resultData.totalRow;
+          vm.isLoading = false;
+        })
+        .catch((error) => {
+          vm.$showAlert(
+            "파일리스트 조회",
+            "파일리스트 조회 오류, 관리자에게 문의하세요."
+          );
+          vm.isLoading = false;
+        });
+    },
+
+    // 파일 선택
+    async selectFile(file) {
+      if (
+        file.extension != "xlsx" &&
+        file.extension != "xls" &&
+        file.extension != "csv"
+      ) {
+        this.$showAlert("파일 읽기", "엑셀 파일만 읽을 수 있습니다.");
+        return;
+      }
+      this.selectedFile = _.cloneDeep(file);
+      // 파일 쓰기용 데이터를 선택 파일에 추가
+      if (this.modeInfo == "fileWrite") {
+        this.selectedFile["fileName"] = file.text;
+        this.selectedFile["fileFom"] = file.extension;
+        this.selectedFile["streOptn"] = false;
+        this.selectedFile["suffix"] = "_YYYYMMDDHHmmss";
+        this.selectedFile["addMonth"] = 0;
+      }
+    },
+
+    // 파일/폴더 목록 선택
+    selectFileList(item) {
+      // 폴더가 선택된 경우, 선택된 노드와 경로를 업데이트하고 파일 리스트 조회
+      if (item.extension === "폴더" || item.extension === "") {
+        this.selectedNode = item.id;
+        this.connection.path = item.id;
+        this.fileSelectList();
+      } else {
+        this.selectFile(item); // 파일이 선택된 경우
+      }
+    },
+
+    // 파일 미리보기
+    async filePreview(file) {
+      if (this.selectedFile == null || file["id"] != this.selectedFile["id"]) {
+        await this.selectFile(file); // 선택 파일 정보 저장
+      }
+      this.preview = true; // 미리보기 활성화
+      await this.fileRead(); // 파일 읽기
+      this.currentPage = 2; // 데이터셋 설정 페이지로 이동
+    },
+
+    // 초기화 function
+    init: async function () {
+      if (this.jobItm == null) {
+        this.jobItm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().jobItem)
+        );
+        this.jobItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().fileRead)
+        );
+        // 아이템이 없을때
+        this.jobItm.indx = 0;
+      }
+
+      this.defaultDataTable = _.cloneDeep(this.jobItm.dataTable); // 초기 빈 데이터테이블 복사
+    },
+
+    /** ************************************ 데이터 셋 읽기 ************************************ */
+    // 파일 읽기
+    async fileRead() {
+      const vm = this;
+      vm.isLoading = true;
+
+      // 선택 파일 정보를 VO 형태로 변환
+      axios
+        .post("/files/read/" + vm.connection.host_code, {
+          path: vm.selectedFile.path,
+          fileName: vm.selectedFile.text,
+          extension: vm.selectedFile.extension,
+          type: "file",
+          datasetId: "",
+          viewMode: true,
+        })
+        .then((response) => {
+          // 잡아이템에 선택 파일 정보 저장
+          if (vm.modeInfo == "fileRead") {
+            vm.jobItm.itm.fileName = vm.selectedFile.text;
+          } else if (vm.modeInfo == "fileWrite") {
+            vm.jobItm.itm.fileName = vm.fileNameBeforeDot;
+          }
+
+          vm.jobItm.itm.path = vm.selectedFile.path;
+          vm.jobItm.itm.extension = vm.selectedFile.extension;
+          vm.jobItm.itm.type = "file";
+          vm.jobItm.itm.datasetId = vm.connection.host_code;
+          vm.jobItm.itm.viewMode = true;
+
+          vm.dataTable = response.data.resultData.dataTableMap;
+          vm.jobItm.dataTable = vm.dataTable; // 잡아이템에 데이터셋 정보 저장
+
+          vm.columnDatas = vm.dataTable.columnDatas;
+          vm.jobItm.front_dataTable.columnDatas = vm.columnDatas;
+        })
+        .catch(() => {
+          vm.isLoading = false;
+        });
+    },
+
+    // 데이터셋 시작 행, 열 정보 변경시
+    indexChange: function (v) {
+      this.jobItm.dataTable = v; // 잡아이템 데이터셋에 변경된 데이터셋 정보 저장
+      this.jobItm.itm.rowDataColumnIndex = v.rowDataColumnIndex;
+      this.jobItm.itm.startRowIndex = v.startRowIndex;
+      this.jobItm.itm.startCellIndex = v.startCellIndex;
+    },
+  },
+  watch: {
+    // 팝업창 열기 요청 시
+    openPopup: function (v) {
+      if (v) {
+        this.openModal();
+      }
+    },
+
+    // 연결 호스트 변경 시
+    selectedHostCode: function (newValue, oldValue) {
+      for (let i = 0; i < this.hostList.length; i++) {
+        if (this.hostList[i].host_code === newValue) {
+          this.selectedHost = this.hostList[i];
+          break;
+        }
+      }
+    },
+
+    "selectedFile.fileName": function (newValue, oldValue) {
+      if (newValue != null) {
+        this.fileNameBeforeDot = this.$filters.removeExtension(newValue);
+      }
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+    TreeItem: TreeItem,
+    FileDataRead: FileDataRead,
+  },
+  mounted() {
+    this.init();
+  },
+};
+</script>
 
client/views/component/connection/itm/fileSelectDiagram.vue (added)
+++ client/views/component/connection/itm/fileSelectDiagram.vue
@@ -0,0 +1,927 @@
+<template>
+  <div
+    v-show="isModalOpen"
+    class="modal-wrapper"
+    :style="isLoading ? { cursor: 'wait' } : {}"
+  >
+    <div v-if="isLoading" class="loading-overlay">
+      <div class="loading-div">
+        <span>LOADING&nbsp;</span>
+        <span class="anima">.</span>
+        <span class="anima">.</span>
+        <span class="anima">.</span>
+      </div>
+    </div>
+    <div class="modal-container list-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2 v-show="modeInfo == 'fileRead'">파일 읽기</h2>
+          <h2 v-show="modeInfo == 'fileWrite'">파일 쓰기</h2>
+          <button class="close-btn" @click="cancelModal()">X</button>
+        </div>
+      </div>
+      <div class="modal-content-monthly" v-show="currentPage == 1">
+        <div class="content-wrap">
+          <div class="content-box flex justify-between">
+            <div class="flex20 content-box">
+              <div class="left-content flex100 content-box">
+                <div class="content-box">
+                  <div class="mb10">
+                    <div class="content-titleZone">
+                      <p class="box-title">호스트 선택</p>
+                    </div>
+                    <div class="flex align-center justify-between no-gutter">
+                      <div class="flex100">
+                        <select
+                          name=""
+                          id=""
+                          class="only full-select"
+                          v-model="selectedHostCode"
+                        >
+                          <option :value="null" disabled>선택</option>
+                          <option
+                            v-for="(host, idx) in hostList"
+                            :key="idx"
+                            :value="host.host_code"
+                          >
+                            {{ host.host_nm + " - (" + host.host_ip + ")" }}
+                          </option>
+                        </select>
+                      </div>
+                      <div class="flex100">
+                        <button
+                          class="blue-border-btn large-btn"
+                          @click="connectionConfirm()"
+                        >
+                          연결
+                        </button>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="file-tree-zone">
+                    <div
+                      class="content-titleZone flex justify-between align-center"
+                    >
+                      <p class="box-title">폴더 리스트</p>
+                    </div>
+                    <div class="file-zone overflow-y">
+                      <ul class="tree-wrap">
+                        <TreeItem
+                          :connection="connection"
+                          :selectedNode="selectedNode"
+                          :selectItem="currentItem"
+                          v-for="(item, idx) in nodes"
+                          :item="item"
+                          :idx="item.id"
+                          :key="idx"
+                          @selectFolder="selectFolder"
+                          @isLoading="handleIsLoading"
+                          @selectItem="handleSelectItem"
+                        />
+                      </ul>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <div class="flex80 content-box">
+              <div class="right-content flex100">
+                <div class="flex-column justify-between">
+                  <div class="flex justify-between align-center no-gutter">
+                    <div class="flex justify-end flex100">
+                      <div class="search-bar">
+                        <div class="flex justify-end align-center">
+                          <select
+                            name=""
+                            id=""
+                            class="square-select"
+                            v-model="searchType"
+                          >
+                            <option value="all">전체</option>
+                            <option value="folder">현재폴더</option>
+                          </select>
+                          <div class="search-square">
+                            <div
+                              class="flex justify-end align-center no-gutter"
+                            >
+                              <input
+                                type="text"
+                                class="square-input flex90"
+                                placeholder="Search"
+                                v-model="searchText"
+                                @keyup.enter="searchFiles()"
+                              />
+                              <button class="square-button blue-btn flex10">
+                                <svg-icon
+                                  type="mdi"
+                                  :path="searchPath"
+                                  class="square-icon"
+                                  @click="searchFiles()"
+                                ></svg-icon>
+                              </button>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="content-zone" style="overflow-y: auto">
+                    <div class="table-zone file-table">
+                      <div class="list-info flex justify-between align-center">
+                        <div class="count-zone">
+                          <p>
+                            총
+                            <span v-if="search.totalRows != 0">{{
+                              search.totalRows
+                            }}</span>
+                            <span v-else>0</span>
+                            건
+                          </p>
+                          <p>
+                            현재경로 : <span>{{ connection.path }}</span>
+                          </p>
+                        </div>
+                      </div>
+                      <table class="list-table">
+                        <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+                        <colgroup>
+                          <col style="width: 5%" />
+                          <col style="width: 45%" />
+                          <col style="width: 10%" />
+                          <col style="width: 20%" />
+                          <col style="width: 10%" />
+                          <col style="width: 10%" />
+                        </colgroup>
+                        <thead>
+                          <tr>
+                            <th></th>
+                            <th>이름</th>
+                            <th>타입</th>
+                            <th>마지막 수정</th>
+                            <th>크기</th>
+                            <th>관리</th>
+                          </tr>
+                        </thead>
+                        <tbody>
+                          <tr
+                            v-for="(file, index) in fileList"
+                            :key="index"
+                            @click="selectFileList(file)"
+                          >
+                            <td
+                              v-if="file.text != '상위폴더로 이동'"
+                              @click.stop=""
+                            ></td>
+                            <td v-else @click.stop=""></td>
+                            <td>
+                              <div class="text-lf">
+                                <span v-if="file.extension === 'txt'"
+                                  ><img
+                                    src="../../../../resources/img/icon/txt.png"
+                                    ref=""
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'pdf'"
+                                  ><img
+                                    src="../../../../resources/img/icon/pdf.png"
+                                    alt=""
+                                /></span>
+                                <span
+                                  v-else-if="
+                                    file.extension === 'pptx' ||
+                                    file.extension === 'ppt'
+                                  "
+                                  ><img
+                                    src="../../../../resources/img/icon/ppt.png"
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'hwp'"
+                                  ><img
+                                    src="../../../../resources/img/icon/hwp.png"
+                                    alt=""
+                                /></span>
+                                <span
+                                  v-else-if="
+                                    file.extension === 'xls' ||
+                                    file.extension === 'xlsx'
+                                  "
+                                  ><img
+                                    src="../../../../resources/img/icon/xls.png"
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'jpg'"
+                                  ><img
+                                    src="../../../../resources/img/icon/img.png"
+                                    alt=""
+                                /></span>
+                                <span v-else-if="file.extension === 'png'"
+                                  ><img
+                                    src="../../../../resources/img/icon/img.png"
+                                    ref=""
+                                    alt=""
+                                /></span>
+                                <span v-else>&emsp;&ensp;</span>
+                                <span>&ensp;{{ file.text }}</span>
+                              </div>
+                            </td>
+                            <td>{{ file.extension }}</td>
+                            <td>{{ file.lastUpdate }}</td>
+                            <td v-if="file.size != 0">
+                              {{ $filters.bytesToSize(file.size) }}
+                            </td>
+                            <td v-else></td>
+                            <td>
+                              <div
+                                v-if="!file.folder && modeInfo == 'fileWrite'"
+                              >
+                                <button
+                                  class="icon-btn orange-btn"
+                                  title="미리보기"
+                                  @click.stop="filePreview(file)"
+                                >
+                                  <svg-icon
+                                    type="mdi"
+                                    :width="18"
+                                    :height="18"
+                                    :path="fileFindPath"
+                                  ></svg-icon>
+                                </button>
+                              </div>
+                              <div v-else></div>
+                            </td>
+                          </tr>
+                        </tbody>
+                      </table>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div v-if="modeInfo == 'fileRead'" class="table-zone">
+          <table class="form-table">
+            <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+            <colgroup>
+              <col style="width: 20%" />
+              <col style="width: 80%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>폴더명</th>
+                <td>{{ jobItm.itm["path"] }}</td>
+              </tr>
+              <tr>
+                <th>파일명</th>
+                <td>{{ jobItm.itm["text"] }}</td>
+              </tr>
+            </tbody>
+          </table>
+          <div class="flex flex30 align-center justify-center">
+            <div class="input-container flex">
+              <label class="check-label">
+                <input type="checkbox" name="check" class="custom-checkbox" />
+                <span>마지막 파일만</span>
+              </label>
+              <label class="check-label">
+                <input type="checkbox" name="check" class="custom-checkbox" />
+                <span>업데이트일 이후</span>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div v-else-if="modeInfo == 'fileWrite'" class="table-zone">
+          <table class="form-table">
+            <colgroup>
+              <col style="width: 10%" />
+              <col style="width: 40%" />
+              <col style="width: 10%" />
+              <col style="width: 40%" />
+            </colgroup>
+            <tbody>
+              <tr>
+                <th>폴더명</th>
+                <td>{{ jobItm.itm["path"] }}</td>
+                <th>쓰기유형</th>
+                <td>
+                  <select name="" id="" class="full-select">
+                    <option selected>엑셀</option>
+                  </select>
+                </td>
+              </tr>
+              <tr>
+                <th>파일명</th>
+                <td>
+                  <input
+                    type="text"
+                    name=""
+                    id=""
+                    class="full-input"
+                    v-model="jobItm.itm.fileName"
+                  />
+                </td>
+                <th>쓰기옵션</th>
+                <td>
+                  <div class="flex flex30 align-center justify-center">
+                    <div class="input-container flex">
+                      <label class="radio-label">
+                        <input
+                          type="radio"
+                          name="radio"
+                          checked=""
+                          class="custom-radiobox"
+                          v-model="selectedFile['streOptn']"
+                          :value="false"
+                        />
+                        <span>쓰기</span>
+                      </label>
+                      <label class="radio-label">
+                        <input
+                          type="radio"
+                          name="radio"
+                          class="custom-radiobox"
+                          v-model="selectedFile['streOptn']"
+                          :value="true"
+                        />
+                        <span>suffix</span>
+                      </label>
+                    </div>
+                  </div>
+                </td>
+              </tr>
+              <tr v-show="selectedFile['streOptn']">
+                <th>suffix</th>
+                <td>
+                  <input
+                    type="text"
+                    name=""
+                    id=""
+                    class="full-input"
+                    v-model="selectedFile['suffix']"
+                  />
+                </td>
+                <th>월변경</th>
+                <td>
+                  <input
+                    type="number"
+                    name=""
+                    id=""
+                    class="full-input"
+                    v-model="selectedFile['addMonth']"
+                  />
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <div class="modal-content-monthly" v-show="currentPage == 2">
+        <div class="content-titleZone">
+          <p v-show="modeInfo == 'fileRead'" class="box-title">데이터셋 설정</p>
+          <p v-show="modeInfo == 'fileWrite'" class="box-title">
+            데이터 미리보기
+          </p>
+        </div>
+        <FileDataRead
+          v-if="isModalOpen"
+          v-model:isLoading="isLoading"
+          v-model:dataTable="dataTable"
+          :preview="preview"
+          @indexChange="indexChange"
+        ></FileDataRead>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button
+          class="blue-btn small-btn"
+          @click="setPage(1)"
+          v-show="currentPage == 2"
+        >
+          이전
+        </button>
+        <button
+          class="blue-btn small-btn"
+          @click="setPage(2)"
+          v-show="currentPage == 1 && modeInfo == 'fileRead'"
+        >
+          다음
+        </button>
+        <button
+          class="blue-btn small-btn"
+          @click="saveModal()"
+          v-show="
+            (currentPage == 2 && modeInfo == 'fileRead') ||
+            (currentPage == 1 && modeInfo == 'fileWrite')
+          "
+        >
+          완료
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+
+  <script>
+import axios from "axios";
+import TreeItem from "../../FileTree.vue";
+import SvgIcon from "@jamescoyle/vue-icon";
+import FileDataRead from "./fileDataRead.vue";
+import { mdiMagnify, mdiFileFind } from "@mdi/js";
+import store from "../../../pages/AppStore";
+import _ from "lodash";
+
+export default {
+  name: "database-connection",
+  props: {
+    openPopup: {
+      type: Boolean,
+      default: false,
+    },
+    jobItem: {
+      type: Boolean,
+      default: null,
+    },
+    mode: {
+      type: String,
+      default: null, // 읽기: fileRead, 쓰기: fileWrite
+    },
+  },
+  data() {
+    return {
+      // 로딩 상태
+      isLoading: false,
+
+      // 현재 모달창 관리
+      isModalOpen: this.openPopup,
+      jobItm: this.jobItem,
+      modeInfo: this.mode,
+
+      currentPage: 1,
+      searchPath: mdiMagnify, // 검색 아이콘 경로
+      fileFindPath: mdiFileFind, // 미리보기 아이콘 경로
+
+      // 검색 객체
+      search: this.$getDefaultSerchVO(),
+      search_data: this.$getDefaultSerchItem(null, "String"),
+
+      selectedHost: {},
+      selectedHostCode: null,
+      // 호스트 목록
+      hostList: [],
+      // 부서-호스트 목록
+      deptHostList: [],
+      connection: {
+        host_code: null,
+        path: null,
+        depth: null,
+        type: null,
+      },
+      nodes: [],
+      selectedNode: null,
+      fileList: [],
+
+      searchType: "all",
+      searchText: null,
+
+      selectItem: null,
+      currentItem: {
+        id: null,
+      },
+
+      selectedFile: {},
+
+      // 초기 빈 데이터 테이블
+      defaultDataTable: {},
+
+      // 임시 저장된 데이터
+      tempJobItm: this.jobItem,
+      tempCurrentPage: 1,
+      tempSearch: this.$getDefaultSerchVO(),
+      tempSearch_data: this.$getDefaultSerchItem(null, "String"),
+      tempSearchText: null,
+      tempConnection: {
+        host_code: null,
+        path: null,
+        depth: null,
+        type: null,
+      },
+      tempNodes: [],
+      tempSelectedNode: null,
+      tempFileList: [],
+      tempSelectedFile: {},
+      tempSelectedHostCode: null,
+      tempDataTable: {},
+      /** ************************************ 데이터 셋 읽기 ************************************ */
+      dataTable: {},
+      preview: false,
+
+      fileNameBeforeDot: null, // 파일명(확장자 제외)
+    };
+  },
+  methods: {
+    // 모달 닫기
+    closeModal: function () {
+      this.$emit("closePopup", this.jobItm);
+    },
+
+    // 화면전환
+    async setPage(val) {
+      if (val == 2) {
+        if (!this.jobItm.itm["text"]) {
+          this.$showAlert("파일 읽기", "파일을 선택해주세요.");
+          return;
+        }
+        await this.fileRead();
+        this.currentPage = val;
+      } else {
+        this.currentPage = val;
+      }
+    },
+
+    // 모달 취소
+    cancelModal: function () {
+      this.closeModal();
+    },
+
+    // 모달 저장
+    async saveModal() {
+      // 파일 쓰기용 데이터를 jobItm에 저장
+      if (this.modeInfo == "fileWrite") {
+        if (
+          !this.selectedFile["streOptn"] &&
+          this.$filters.removeExtension(this.selectedFile["text"]) ==
+            this.fileNameBeforeDot
+        ) {
+          if (
+            !(await this.$showConfirm(
+              "파일 쓰기",
+              "파일명이 동일할 시 덮어쓰기 됩니다."
+            ))
+          ) {
+            return;
+          }
+        }
+
+        this.jobItm.itm["path"] = this.selectedFile["parent"];
+        this.jobItm.itm["text"] = this.selectedFile["text"];
+        this.fileNameBeforeDot + "." + this.selectedFile["fileFom"];
+        this.jobItm.itm["fileFom"] = this.selectedFile["fileFom"];
+        this.jobItm.itm["streOptn"] = this.selectedFile["streOptn"];
+        this.jobItm.itm["suffix"] = this.selectedFile["suffix"];
+        this.jobItm.itm["addMonth"] = this.selectedFile["addMonth"];
+
+        this.jobItm.dataTable = _.cloneDeep(this.defaultDataTable);
+      }
+      this.closeModal();
+    },
+
+    // 검색 객체 초기화
+    searchInit: function () {
+      this.search.searchObjectList.push(this.search_data);
+    },
+
+    // 파일 검색
+    searchFiles() {
+      const vm = this;
+
+      if (vm.searchText === null || vm.searchText === "") {
+        vm.$showAlert("파일 검색", "검색어를 입력해 주세요.");
+        return;
+      }
+
+      let path = vm.connection.path;
+
+      if (vm.searchType === "all") {
+        path = "#";
+      }
+      vm.isLoading = true; // 로딩 시작
+
+      axios({
+        url: "/files/search/" + vm.connection.host_code,
+        method: "post",
+        headers: {},
+        data: {
+          searchType: vm.searchType,
+          searchText: vm.searchText,
+          path: path,
+        },
+      })
+        .then(function (response) {
+          vm.isLoading = false; // 로딩 해제
+          vm.fileList = response.data.resultData.fileList;
+          if (vm.fileList.length == 0) {
+            vm.$showAlert("파일 검색", "검색 결과가 없습니다.");
+          }
+        })
+        .catch(function (error) {
+          vm.isLoading = false; // 로딩 해제
+          vm.$showAlert(
+            "파일 검색",
+            "파일 검색 오류, 관리자에게 문의바랍니다."
+          );
+        });
+    },
+
+    // 로딩 상태 변경
+    handleIsLoading(isLoadingValue) {
+      this.isLoading = isLoadingValue; // 부모 컴포넌트의 데이터 업데이트
+    },
+
+    // 호스트 목록 조회
+    selectHosts() {
+      const vm = this;
+      axios({
+        url: "/files/hosts",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: {
+          userId: store.state.loginUser.user_id,
+          authList: store.state.loginUser.user_auth,
+          author: store.state.loginUser.user_auth[0],
+          dept_code: store.state.loginUser.dept_code,
+        },
+      })
+        .then((response) => {
+          vm.hostList = response.data.resultData.hostList;
+          vm.deptHostList = response.data.resultData.deptHostList;
+          if (this.hostList.length > 0) {
+            vm.selectedHost = vm.hostList[0];
+            vm.selectedHostCode = vm.hostList[0].host_code;
+            for (let i = 0; i < vm.hostList.length; i++) {
+              for (let j = 0; j < vm.deptHostList.length; j++) {
+                if (vm.hostList[i].host_code === vm.deptHostList[j].host_code) {
+                  vm.hostList[i].main_folder_path =
+                    vm.deptHostList[j].main_folder_path;
+                }
+              }
+            }
+            vm.connectionConfirm();
+          } else {
+            vm.selectedHost = {};
+          }
+        })
+        .catch((error) => {
+          vm.$showAlert(
+            "호스트 조회",
+            "호스트 조회 오류, 관리자에게 문의하세요."
+          );
+        });
+    },
+
+    // 선택한 호스트 연결
+    async connectionConfirm() {
+      const vm = this;
+      if (vm.selectedHostCode === null || vm.selectedHostCode === undefined) {
+        vm.$showAlert("파일시스템 연결", "호스트를 선택해 주세요.");
+        return;
+      }
+
+      // 이전에 선택한 호스트 코드가 있으면 저장
+      let tempHostCode = null;
+      if (vm.connection.host_code) {
+        tempHostCode = _.cloneDeep(vm.connection.host_code);
+      }
+
+      vm.connection.host_code = vm.selectedHost.host_code;
+
+      vm.isLoading = true; // 로딩 시작
+
+      try {
+        const response = await axios.get("/files/connection", {
+          params: { host_code: vm.connection.host_code },
+        });
+
+        vm.isLoading = false; // 로딩 해제
+        if (response.data.status === 200) {
+          await this.fileTreeList();
+        } else {
+          // 이전에 선택한 호스트 코드로 변경
+          if (tempHostCode) {
+            vm.connection.host_code = tempHostCode;
+            vm.selectedHost.host_code = tempHostCode;
+            vm.selectedHostCode = tempHostCode;
+          }
+        }
+        vm.selectedFile = {};
+        vm.$showAlert("파일시스템 연결", response.data.message);
+      } catch (error) {
+        vm.isLoading = false; // 로딩 해제
+        vm.$showAlert(
+          "파일시스템 연결",
+          "파일시스템 연결 오류, 관리자에게 문의하세요."
+        );
+        vm.selectedFile = {};
+        // 이전에 선택한 호스트 코드로 변경
+        if (tempHostCode) {
+          vm.connection.host_code = tempHostCode;
+          vm.selectedHost.host_code = tempHostCode;
+          vm.selectedHostCode = tempHostCode;
+        }
+      }
+    },
+
+    // 폴더 리스트 조회
+    async fileTreeList() {
+      const vm = this;
+      vm.nodes = [];
+      vm.connection.path = vm.selectedHost.main_folder_path;
+      vm.connection.depth = 0;
+      vm.connection.type = "folder";
+
+      vm.isLoading = true; // 로딩 시작
+
+      try {
+        const response = await axios.get("/files/tree", {
+          params: vm.connection,
+        });
+        vm.nodes = response.data.resultData.fileTree;
+        // vm.connection.path = null;
+        vm.fileList = [];
+        vm.isLoading = false; // 로딩 해제
+      } catch (error) {
+        vm.$showAlert(
+          "파일리스트 조회",
+          "파일리스트 조회 오류, 관리자에게 문의하세요."
+        );
+        vm.fileList = [];
+        vm.isLoading = false; // 로딩 해제
+        vm.$showAlert(
+          "파일리스트 조회",
+          "파일리스트 조회 오류, 관리자에게 문의하세요."
+        );
+      }
+    },
+
+    // 폴더 선택
+    selectFolder(path) {
+      if (path === null || path === undefined) {
+        this.isLoading = false;
+      }
+      this.connection.path = path;
+      this.fileSelectList();
+    },
+
+    handleSelectItem(item, parentItem) {
+      // 현재 선택된 폴더와 클릭한 폴더가 같을 때
+      if (this.currentItem.id == item.id) {
+        this.selectItem = parentItem;
+        this.currentItem = parentItem;
+      } else {
+        this.selectItem = item;
+        this.currentItem = item;
+      }
+    },
+
+    // 파일 리스트 조회
+    fileSelectList() {
+      const vm = this;
+
+      if (vm.connection.path === null || vm.connection.path === "") {
+        vm.fileList = null;
+        return;
+      }
+
+      vm.connection.type = "all";
+      vm.connection.depth = 1;
+
+      axios
+        .get("/files/list", { params: vm.connection })
+        .then((response) => {
+          vm.fileList = response.data.resultData.fileList;
+          vm.search.totalRows = response.data.resultData.totalRow;
+          vm.isLoading = false;
+        })
+        .catch((error) => {
+          vm.$showAlert(
+            "파일리스트 조회",
+            "파일리스트 조회 오류, 관리자에게 문의하세요."
+          );
+          vm.isLoading = false;
+        });
+    },
+
+    // 파일 선택
+    async selectFile(file) {
+      if (
+        file.extension != "xlsx" &&
+        file.extension != "xls" &&
+        file.extension != "csv"
+      ) {
+        this.$showAlert("파일 읽기", "엑셀 파일만 읽을 수 있습니다.");
+        return;
+      }
+      this.selectedFile = _.cloneDeep(file);
+      this.jobItm.itm = _.cloneDeep(file);
+      // 파일 쓰기용 데이터를 선택 파일에 추가
+      if (this.modeInfo == "fileWrite") {
+        this.jobItm.itm["path"] = file.path;
+        this.jobItm.itm["fileName"] = file.text;
+        this.jobItm.itm["fileFom"] = file.extension;
+        this.jobItm.itm["streOptn"] = false;
+        this.jobItm.itm["suffix"] = "_YYYYMMDDHHmmss";
+        this.jobItm.itm["addMonth"] = 0;
+        this.selectedFile["path"] = file.path;
+        this.selectedFile["fileName"] = file.text;
+        this.selectedFile["fileFom"] = file.extension;
+        this.selectedFile["streOptn"] = false;
+        this.selectedFile["suffix"] = "_YYYYMMDDHHmmss";
+        this.selectedFile["addMonth"] = 0;
+      }
+    },
+
+    // 파일/폴더 목록 선택
+    selectFileList(item) {
+      // 폴더가 선택된 경우, 선택된 노드와 경로를 업데이트하고 파일 리스트 조회
+      if (item.extension === "폴더" || item.extension === "") {
+        this.selectedNode = item.id;
+        this.connection.path = item.id;
+        this.fileSelectList();
+      } else {
+        this.selectFile(item); // 파일이 선택된 경우
+      }
+    },
+
+    // 파일 미리보기
+    async filePreview(file) {
+      if (this.selectedFile == null || file["id"] != this.selectedFile["id"]) {
+        await this.selectFile(file); // 선택 파일 정보 저장
+      }
+      this.preview = true; // 미리보기 활성화
+      await this.fileRead(); // 파일 읽기
+      this.currentPage = 2; // 데이터셋 설정 페이지로 이동
+    },
+
+    // 초기화 function
+    init: function () {
+      this.selectHosts();
+      this.searchInit();
+    },
+
+    // 파일 읽기
+    async fileRead() {
+      const vm = this;
+      vm.isLoading = true;
+
+      // 선택 파일 정보를 VO 형태로 변환
+      axios
+        .post("/files/read/" + vm.connection.host_code, {
+          path: vm.jobItm.itm.path,
+          fileName: vm.jobItm.itm.text,
+          extension: vm.jobItm.itm.extension,
+          type: "file",
+          datasetId: "",
+          viewMode: true,
+        })
+        .then((response) => {
+          // 잡아이템에 선택 파일 정보 저장
+          if (vm.modeInfo == "fileRead") {
+            vm.jobItm.itm.text = vm.selectedFile.text;
+          }
+          vm.jobItm.itm.path = vm.selectedFile.path;
+          vm.jobItm.itm.extension = vm.selectedFile.extension;
+          vm.jobItm.itm.type = "file";
+          vm.jobItm.itm.datasetId = vm.connection.host_code;
+          vm.jobItm.itm.viewMode = true;
+
+          vm.dataTable = response.data.resultData.dataTableMap;
+          vm.jobItm.dataTable = vm.dataTable; // 잡아이템에 데이터셋 정보 저장
+
+          vm.columnDatas = vm.dataTable.columnDatas;
+          vm.jobItm.front_dataTable.columnDatas = vm.columnDatas;
+        })
+        .catch(() => {
+          vm.isLoading = false;
+        });
+    },
+
+    // 데이터셋 시작 행, 열 정보 변경시
+    indexChange: function (v) {
+      this.jobItm.dataTable = v; // 잡아이템 데이터셋에 변경된 데이터셋 정보 저장
+      this.jobItm.itm.rowDataColumnIndex = v.rowDataColumnIndex;
+      this.jobItm.itm.startRowIndex = v.startRowIndex;
+      this.jobItm.itm.startCellIndex = v.startCellIndex;
+    },
+  },
+  watch: {
+    // 연결 호스트 변경 시
+    selectedHostCode: function (newValue, oldValue) {
+      for (let i = 0; i < this.hostList.length; i++) {
+        if (this.hostList[i].host_code === newValue) {
+          this.selectedHost = this.hostList[i];
+          break;
+        }
+      }
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+    TreeItem: TreeItem,
+    FileDataRead: FileDataRead,
+  },
+  mounted() {
+    this.init();
+  },
+};
+</script>
 
client/views/component/connection/jobContainer.vue (added)
+++ client/views/component/connection/jobContainer.vue
@@ -0,0 +1,273 @@
+<template>
+  <div class="row">
+    <div class="table-zone pd0">
+      <table class="form-table">
+        <!-- col 꼭 너비 기재해야함! 그래야 100%로 차지함 -->
+        <colgroup>
+          <col style="width: 15%" />
+          <col style="width: 85%" />
+        </colgroup>
+        <tbody>
+          <tr>
+            <th>데이터 선택</th>
+            <td>
+              <button class="blue-border-btn small-btn" @click="itmSelectModal = true">아이템 추가</button>
+              <button class="blue-btn small-btn" @click="execModel">데이터 적용</button>
+            </td>
+          </tr>
+          <tr>
+            <td colspan="2">
+              <div class="data-list" :style="'height:' + componentHright + 'px;'">
+                <ul class="flex">
+                  <li v-for="(itm, indx) in crrentJobGroup.jobItm" :key="indx" class="flex50">
+                    <div class="item flex justify-between align-center">
+                      <p>{{ indx + 1 + ". " + itm.type }}</p>
+                      <div>
+                        <button class="blue-border-btn set-btn" @click="showPopup(indx)">설정</button>
+                        <!-- <button class="red-border-btn set-btn" @click="deleteConnect(indx)">삭제</button> -->
+                      </div>
+                      <FileSelect :openPopup="popupToggls[indx]" :jobItem="itm" :mode="itm.type" @closePopup="closePopup(indx)" v-if="itm.type == 'fileRead' || itm.type == 'fileWrite'" />
+                      <DatabaseConnection :openPopup="popupToggls[indx]" :jobItem="itm" @closePopup="closePopup(indx)" v-if="itm.type == 'dbConnection'" />
+                      <DataCheck :openPopup="popupToggls[indx]" :jobItem="itm" @closePopup="closePopup(indx)" v-if="itm.type == 'dataCheck'" />
+                      <DataSort :openPopup="popupToggls[indx]" :item="itm" @closePopup="closePopup(indx)" v-if="itm.type == 'dataSort'" />
+                      <DataFilter :openPopup="popupToggls[indx]" :item="itm" @closePopup="closePopup(indx)" v-if="itm.type == 'dataFilter'" />
+                    </div>
+                  </li>
+                </ul>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+  <div v-show="itmSelectModal" class="modal-wrapper">
+    <div class="modal-container small-modal">
+      <div class="modal-title">
+        <div class="flex justify-between align-center">
+          <h2>아이템 선택</h2>
+          <button class="close-btn" @click="itmSelectModal = false">X</button>
+        </div>
+      </div>
+      <div class="modal-content-monthly">
+        <div class="justify-center aling-center" style="text-align: center">
+          <select class="square-select half-input" v-model="selectItm">
+            <!-- <option v-show="crrentJobGroup.datasetPost_id == null" value=""> 데이터셋 읽기 </option>
+            <option v-show="crrentJobGroup.datasetPost_id == null" value="fileRead"> 파일 읽기 </option> -->
+            <option v-show="crrentJobGroup.datasetPost_id == null" value="dbConnection"> DB 수집 </option>
+            <!-- <option v-show="crrentJobGroup.datasetPost_id == null" value=""> API 수집 </option>
+            <option v-show="crrentJobGroup.datasetPost_id == null" value="dataSort"> 데이터검증 </option>
+            <option v-show="crrentJobGroup.datasetPost_id == null" value="fileWrite"> 파일 쓰기 </option> -->
+            <!-- <option value="dataFilter">필터 모듈</option>
+            <option value="dataSort">정렬 모듈</option> -->
+          </select>
+        </div>
+      </div>
+      <div class="modal-end flex justify-end">
+        <button class="darkg-border-btn small-btn" @click="addItm">확인</button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import FileSelect from "./itm/fileSelect.vue";
+import DatabaseConnection from "./itm/databaseConnection.vue";
+import DataCheck from "./itm/dataCheck.vue";
+import DataSort from "./itm/dataSort.vue";
+import DataFilter from "./itm/dataFilter.vue";
+
+import axios from "axios";
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiTrashCan, mdiCog } from "@mdi/js";
+export default {
+  name: "job-container",
+  props: {
+    jobGroup: {
+      type: Object,
+      default: null,
+    },
+    height: {
+      type: Number,
+      default: 100,
+    },
+  },
+  data() {
+    return {
+      popupToggls: [],
+      itmSelectModal: false,
+      // , jobGroups: this.jobGroup
+      crrentJobGroup: this.jobGroup,
+      componentHright: this.height,
+      defaultJobGroup: Object, //JSON.parse(JSON.stringify(this.$getDefaultJobGroup().jobGroup))
+      defaultJobItm: Object, //JSON.parse(JSON.stringify(this.$getDefaultJobGroup().jobItm))
+      delPath: mdiTrashCan,
+      setPath: mdiCog,
+      selectItm: "dbConnection",
+    };
+  },
+  methods: {
+    // 팝업 호출
+    showPopup: function (indx) {
+      if (
+        this.crrentJobGroup.jobItm[indx - 1] != null &&
+        this.crrentJobGroup.datasetPost_id == null
+      ) {
+        console.log('show')
+        this.crrentJobGroup.jobItm[indx].front_dataTable =
+          this.crrentJobGroup.jobItm[indx - 1].dataTable;
+      }
+
+      this.popupToggls[indx] = true;
+    },
+    closePopup: function (indx) {
+      this.popupToggls[indx] = false;
+
+    },
+
+    // 기본 job 데이터
+    getDefaultJobGroup: function () {
+      var vm = this;
+      this.defaultJobGroup = JSON.parse(
+        JSON.stringify(this.$getDefaultJobGroup().jobGroup)
+      );
+      this.defaultJobItm = JSON.parse(
+        JSON.stringify(this.$getDefaultJobGroup().jobItem)
+      );
+      if (this.crrentJobGroup == null) {
+        this.crrentJobGroup = JSON.parse(JSON.stringify(this.defaultJobGroup));
+      }
+    },
+
+    // 아이템 선택
+    addItm: function () {
+      var type = this.selectItm;
+      this.itmSelectModal = false;
+      let newItm = JSON.parse(
+        JSON.stringify(this.$getDefaultJobGroup().jobItem)
+      );
+      newItm.type = type;
+      newItm.indx = this.crrentJobGroup.jobItm.length + 1;
+
+      // 파일 읽기 모듈
+      if (type == "fileRead") {
+        newItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().fileRead)
+        );
+      }
+
+      // 데이터 베이스 연계 모듈
+      else if (type == "dbConnection") {
+        newItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().connectionDb)
+        );
+      }
+
+      // 데이터 체크모듈 (안씀)
+      else if (type == "dataCheck") {
+        newItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().dataCheck)
+        );
+        newItm.itm_option_string = "1";
+      }
+
+      // 데이터 정렬 초기화
+      else if (type == "dataSort") {
+        newItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().dataFilter)
+        );
+        newItm.itm.filterItems.push(
+          JSON.parse(JSON.stringify(this.$getDefaultJobGroup().filterItem))
+        );
+        newItm.itm.filterItems[0].cmpr_value = "DESC";
+        newItm.itm.filterItems[0].cmpr_value2 = "10000";
+        // 잡그룹이 데이터셋 형일때
+        if (this.crrentJobGroup.datasetPost_id != null) {
+          newItm.front_dataTable = JSON.parse(
+            JSON.stringify(this.crrentJobGroup.dataTable)
+          );
+        }
+      }
+
+      // 데이터 필터 초기화
+      else if (type == "dataFilter") {
+        newItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().dataFilter)
+        );
+        newItm.itm.match_type = true;
+        // 잡그룹이 데이터셋 형일때
+        if (this.crrentJobGroup.datasetPost_id != null) {
+          newItm.front_dataTable = JSON.parse(
+            JSON.stringify(this.crrentJobGroup.dataTable)
+          );
+        }
+      }
+
+      // 파일 쓰기 모듈
+      else if (type == "fileWrite") {
+        newItm.itm = JSON.parse(
+          JSON.stringify(this.$getDefaultJobGroup().fileRead)
+        );
+      }
+
+      if (
+        this.crrentJobGroup.jobItm.length > 0 &&
+        this.crrentJobGroup.datasetPost_id == null
+      ) {
+        newItm.front_dataTable =
+          this.crrentJobGroup.jobItm[
+            this.crrentJobGroup.jobItm.length - 1
+          ].dataTable;
+      }
+
+      this.popupToggls.push(false);
+
+      this.crrentJobGroup.jobItm.push(newItm);
+    },
+
+    execModel: function () {
+      let vm = this;
+      axios({
+        url: "/job/executeJob.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: vm.crrentJobGroup,
+      })
+        .then(function (response) {
+          vm.crrentJobGroup.dataTable = response.data.resultData.dataTable;
+          vm.$emit('getDataTable', vm.crrentJobGroup.dataTable)
+        })
+        .catch(function (error) {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+    deleteConnect: function (indx) {
+      this.crrentJobGroup.jobItm.splice(indx, 1);
+    },
+  },
+  watch: {
+    jobGroup: function (v) {
+      this.crrentJobGroup = v;
+    },
+    height: function (v) {
+      this.componentHright = v;
+    },
+
+  },
+  components: {
+    FileSelect: FileSelect,
+    DatabaseConnection: DatabaseConnection,
+    SvgIcon: SvgIcon,
+    DataCheck: DataCheck,
+    DataSort: DataSort,
+    DataFilter: DataFilter,
+  },
+  mounted() {
+    this.getDefaultJobGroup();
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/dataComponent/ColumnEditComponent.vue (added)
+++ client/views/component/dataComponent/ColumnEditComponent.vue
@@ -0,0 +1,298 @@
+<template>
+  <div class="content content-box">
+    <div class="flex-column justify-between">
+      <div style="height: 100%">
+        <div class="content-titleZone">
+          <div class="flex justify-between align-center no-gutter">
+            <div class="flex30">
+              <p class="box-title">컬럼정보</p>
+            </div>
+            <div class="flex justify-end flex70">
+              <button class="darkg-border-btn small-btn" @click="executionChangeData('on')"> 자동PK 생성 </button>
+              <button class="blue-border-btn small-btn" @click="addColumn"> 추가 </button>
+              <button class="blue-btn small-btn" @click="executionChangeData('off')"> 실행 </button>
+            </div>
+          </div>
+        </div>
+        <div class="content-zone" style="overflow: auto">
+          <div class="table-zone">
+            <div class="count-zone">
+              <p> 총 ROW : <span> {{ coulmnsInfo.length }} </span>
+              </p>
+            </div>
+            <table class="list-table">
+              <colgroup>
+                <col width="80px" />
+                <col width="80px" />
+                <col width="" />
+                <col width="" />
+                <col width="" />
+                <col width="100px" />
+                <col width="50px" />
+                <col width="50px" />
+                <col width="400px" />
+              </colgroup>
+              <thead>
+                <tr>
+                  <th>No</th>
+                  <th>상태</th>
+                  <th>컬럼명(영문)</th>
+                  <th>컬럼한글(한글)</th>
+                  <th>데이터유형</th>
+                  <th>데이터길이</th>
+                  <th>PK</th>
+                  <th>표준</th>
+                  <th>설정</th>
+                </tr>
+              </thead>
+              <tbody v-if="coulmnsInfo != null && coulmnsInfo.length > 0">
+                <tr v-for="(cells, i) in coulmnsInfo" :key="i">
+                  <td>{{ i + 1 }}</td>
+                  <td style="text-align: center">
+                    <span>{{ cells.status }}</span>
+                  </td>
+                  <td>
+                    <span v-if="!cells.enableEdit">{{ cells.columnNm }}</span>
+                    <input v-else type="text" class="width100p" v-model="cells.columnNm" />
+                  </td>
+                  <td>
+                    <span v-if="!cells.enableEdit">{{ cells.displyColumnNm }}</span>
+                    <input v-else type="text" autocomplete="off" name="text" class="width100p" v-model="cells.displyColumnNm" />
+                    <!-- @input="getStandardTermListByContains(cells, $event.target.value, i)"
+                                            @keyup="selectingStandardTermByKey(cells, $event, i)"> -->
+                    <div class="search_list_box" id="search_list" name="search_list" v-show="cells.searchListViewAt == true" style="
+                        top: auto;
+                        left: auto;
+                        border: solid 0.1px #bcbcbc;
+                        max-width: 400px;
+                      ">
+                      <div class="item" v-for="(search, j) in cells.standardTermSearchList" :key="j" :title="'한글명:' +
+                        search.korean +
+                        '/영문명:' +
+                        search.english +
+                        '/영문약어명:' +
+                        search.englishAbbr
+                        " :id="search.termSeqs" :class="{
+                          item_selected:
+                            cells.selectingStandardTerm != null &&
+                            cells.selectingStandardTerm.termSeqs ==
+                            search.termSeqs,
+                        }"></div>
+                    </div>
+                  </td>
+                  <td>
+                    <span v-if="!cells.enableEdit">{{ cells.dataTy }}</span>
+                    <select name="public" class="select_R width120 mr5" v-model="cells.dataTy" v-else>
+                      <option v-for="(item, indx) in dataTypeList" :value="item" :key="indx"> {{ item }} </option>
+                    </select>
+                  </td>
+                  <td>
+                    <span v-if="!cells.enableEdit">{{ cells.dataSize }}</span>
+                    <input v-else type="number" class="width100p" v-model="cells.dataSize" />
+                  </td>
+                  <td style="text-align: center">
+                    <input type="checkbox" :id="'pr' + i" v-model="cells.pkAt" :disabled="cells.enableEdit == false" />
+                    <label :for="'pr' + i" style="padding-left: 0px"></label>
+                  </td>
+                  <td style="text-align: center">
+                    <input type="checkbox" :id="'st' + i" v-model="cells.columnStdizAt" :disabled="cells.enableEdit == false" />
+                    <label :for="'st' + i" style="padding-left: 0px"></label>
+                  </td>
+                  <td style="text-align: center">
+                    <button type="button" class="blue-border-btn small-btn" title="수정" v-if="!cells.enableEdit" @click="editStart(cells)"> 수정 </button>
+                    <button type="button" class="blue-border-btn small-btn" title="닫기" v-if="cells.enableEdit" @click="editEnd(cells)"> 완료 </button>
+                    <button type="button" class="red-border-btn small-btn" v-if="cells.status != 3" title="삭제" @click="deleteColumn(cells)"> 삭제 </button>
+                    <button type="button" class="red-border-btn small-btn" v-if="cells.status == 3" title="취소" @click="deleteColumn(cells)"> 취소 </button>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import axios from "axios";
+import SvgIcon from "@jamescoyle/vue-icon";
+import _ from "lodash";
+
+export default {
+  props: {
+    datasetPost_id: {
+      type: String,
+      default: "",
+    },
+  },
+  data() {
+    return {
+      comId:
+        "comId_" +
+        Math.random().toString(36).substring(2, 15) +
+        Math.random().toString(36).substring(2, 15),
+      dataTable: _.cloneDeep(this.$getDefaultObject().dataTable),
+      dataTypeList: _.cloneDeep(this.$getDefaultObject().dataTypeList),
+      coulmn_infoId:
+        "coulmn_info" +
+        Math.random().toString(36).substring(2, 15) +
+        Math.random().toString(36).substring(2, 15),
+      coulmn_infoIds:
+        "coulmn_info" +
+        Math.random().toString(36).substring(2, 15) +
+        Math.random().toString(36).substring(2, 15),
+      coulmnsInfo: [],
+      changeData: [],
+      orgPkList: [],
+      currentPkList: [],
+    };
+  },
+  methods: {
+    init: function () {
+      let vm = this;
+      axios({
+        url: "/dataset/getDataTableInfo.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: { datapost_id: vm.datasetPost_id },
+      })
+        .then(function (response) {
+          vm.dataTable = response.data.resultData.dataTable;
+          vm.coulmnsInfo = response.data.resultData.dataTable.columnDatas;
+          vm.orgPkList = vm.getcurrentPkList();
+        })
+        .catch(function (error) {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+    editStart: function (data) {
+      data.enableEdit = true;
+      if (data.status == 1) {
+        data.orgData = _.cloneDeep(data);
+      }
+    },
+    editEnd: function (data) {
+      data.enableEdit = false;
+      data.searchListViewAt = false;
+      data.standardTermSearchList = [];
+      this.checkData(data);
+    },
+    checkData: function (data) {
+      var checkData = _.cloneDeep(data);
+      checkData.orgData = null;
+      checkData.enableEdit = true;
+      checkData.status = 1;
+      if (_.isEqual(checkData, data.orgData)) {
+        data.status = 1;
+        data.orgData = null;
+      } else {
+        data.status = 2;
+      }
+    },
+    // 컬럼 추가
+    addColumn: function () {
+      var columnData = _.cloneDeep(this.coulmnsInfo[0]);
+      columnData.dataTy = "STRING";
+      columnData.displyColumnNm = "NEW_DT";
+      columnData.orginlColumnNm = "NEW_DT";
+      columnData.columnNm = "NEW_DT";
+      columnData.pkAt = false;
+      columnData.columnStdizAt = false;
+      columnData.dataSize = 50;
+      columnData.ordr = this.coulmnsInfo.length;
+      columnData.status = 4;
+      columnData.enableEdit = true;
+      this.coulmnsInfo.push(columnData);
+    },
+    // 컬럼 삭제
+    deleteColumn: function (column) {
+      if (column.status == 3) {
+        if (column.orgData == null) {
+          column.status = 1;
+        } else {
+          column.status = 2;
+        }
+      } else if (column.status == 4) {
+        this.coulmnsInfo.splice(column.orders, 1);
+      } else {
+        column.status = 3;
+      }
+    },
+    // 변경된 데이터 리스트 가져오기
+    getChangeList: function () {
+      this.changeData = [];
+      for (var i = 0; i < this.coulmnsInfo.length; i++) {
+        if (this.coulmnsInfo[i].status != 1) {
+          this.changeData.push(this.coulmnsInfo[i]);
+        }
+      }
+    },
+    // pk 찾기
+    getcurrentPkList: function () {
+      var pkList = [];
+      for (var i = 0; i < this.coulmnsInfo.length; i++) {
+        if (this.coulmnsInfo[i].pkAt) {
+          pkList.push(this.coulmnsInfo[i].columnNm);
+        }
+      }
+      return pkList;
+    },
+    // 데이터 변경 실행
+    executionChangeData: function (autoPk) {
+      var vm = this;
+      this.getChangeList();
+
+      if (this.changeData.length == 0 && autoPk == false) {
+        alert("변경된 데이터가 없습니다.");
+        return false;
+      }
+
+      var request = {
+        changeData: this.changeData,
+        connectionId: this.dataTable.dbConectId,
+        tableName: this.dataTable.tableNm,
+        databaseName: this.dataTable.databaseNm,
+        orgPkList: this.orgPkList,
+        currentPkList: this.getcurrentPkList(),
+        datasetId: this.dataTable.datasetId,
+        datasetPostId: this.dataTable.datasetPostId,
+        pkInsert: autoPk,
+      };
+      axios({
+        url: "/dataset/changeColumnInfo.json",
+        method: "post",
+        headers: {
+          "Content-Type": "application/json; charset=UTF-8",
+        },
+        data: request,
+      })
+        .then(function (response) {
+          if (response.data.checkMessage.success) {
+            vm.coulmnsInfo = response.data.resultData.columnDatas;
+            vm.orgPkList = vm.getcurrentPkList();
+            vm.$showAlert("message", "변경완료");
+          } else {
+            vm.$showAlert("경고", response.data.checkMessage.message);
+          }
+        })
+        .catch(function (error) {
+          this.$showAlert(
+            "에러 발생",
+            "에러가 발생했습니다. 관리자에게 문의해 주세요."
+          );
+        });
+    },
+  },
+  components: {
+    SvgIcon: SvgIcon,
+  },
+  mounted() {
+    this.init();
+  },
+};
+</script>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/dataComponent/DataEditComponent.vue (added)
+++ client/views/component/dataComponent/DataEditComponent.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/dataComponent/DbConnectionSearchModal.vue (added)
+++ client/views/component/dataComponent/DbConnectionSearchModal.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/BaseComponent.vue (added)
+++ client/views/component/elementComponent/BaseComponent.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/ComponentTitle.vue (added)
+++ client/views/component/elementComponent/ComponentTitle.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/EquipmentData.vue (added)
+++ client/views/component/elementComponent/EquipmentData.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/HorizentalThreeData.vue (added)
+++ client/views/component/elementComponent/HorizentalThreeData.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/HorizentalTwoData.vue (added)
+++ client/views/component/elementComponent/HorizentalTwoData.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/TableData.vue (added)
+++ client/views/component/elementComponent/TableData.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/TestComponent.vue (added)
+++ client/views/component/elementComponent/TestComponent.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/elementComponent/ThreeData.vue (added)
+++ client/views/component/elementComponent/ThreeData.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/flowComponent/VueFlowZoneGroup.vue (added)
+++ client/views/component/flowComponent/VueFlowZoneGroup.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/flowComponent/VueFlowZonePopup.vue (added)
+++ client/views/component/flowComponent/VueFlowZonePopup.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/style/BackgroundOption.vue (added)
+++ client/views/component/style/BackgroundOption.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/style/CellOption.vue (added)
+++ client/views/component/style/CellOption.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/style/FontOption.vue (added)
+++ client/views/component/style/FontOption.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/style/StyleSheetComponent.vue (added)
+++ client/views/component/style/StyleSheetComponent.vue
This diff is skipped because there are too many other diffs.
 
client/views/component/style/TitleStyleComponent.vue (added)
+++ client/views/component/style/TitleStyleComponent.vue
This diff is skipped because there are too many other diffs.
 
client/views/index.html (added)
+++ client/views/index.html
This diff is skipped because there are too many other diffs.
 
client/views/index.js (added)
+++ client/views/index.js
This diff is skipped because there are too many other diffs.
 
client/views/layout/Header.vue (added)
+++ client/views/layout/Header.vue
This diff is skipped because there are too many other diffs.
 
client/views/layout/SideMenu.vue (added)
+++ client/views/layout/SideMenu.vue
This diff is skipped because there are too many other diffs.
 
client/views/layout/TopMenu.vue (added)
+++ client/views/layout/TopMenu.vue
This diff is skipped because there are too many other diffs.
 
client/views/layout/back241206_TopMenu.vue (added)
+++ client/views/layout/back241206_TopMenu.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/App.vue (added)
+++ client/views/pages/App.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/AppRouter.js (added)
+++ client/views/pages/AppRouter.js
This diff is skipped because there are too many other diffs.
 
client/views/pages/AppStore.js (added)
+++ client/views/pages/AppStore.js
This diff is skipped because there are too many other diffs.
 
client/views/pages/bu_241206_AppRouter.js (added)
+++ client/views/pages/bu_241206_AppRouter.js
This diff is skipped because there are too many other diffs.
 
client/views/pages/custom/CustomInsert.vue (added)
+++ client/views/pages/custom/CustomInsert.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/custom/CustomInsert2.vue (added)
+++ client/views/pages/custom/CustomInsert2.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/custom/CustomInsertDev.vue (added)
+++ client/views/pages/custom/CustomInsertDev.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/custom/CustomSelectList.vue (added)
+++ client/views/pages/custom/CustomSelectList.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/custom/CustomSelectOne.vue (added)
+++ client/views/pages/custom/CustomSelectOne.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/DataEditView.vue (added)
+++ client/views/pages/data/DataEditView.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/DataPostDetail.vue (added)
+++ client/views/pages/data/DataPostDetail.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/DataPostManagement.vue (added)
+++ client/views/pages/data/DataPostManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/FileManagement.vue (added)
+++ client/views/pages/data/FileManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/HostManagement.vue (added)
+++ client/views/pages/data/HostManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/InsertDataPost.vue (added)
+++ client/views/pages/data/InsertDataPost.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/data/JobTest.vue (added)
+++ client/views/pages/data/JobTest.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/dbConnection/DBConnectionDetail.vue (added)
+++ client/views/pages/dbConnection/DBConnectionDetail.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/dbConnection/DBConnectionList.vue (added)
+++ client/views/pages/dbConnection/DBConnectionList.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/dbConnection/InsertDBConnection.vue (added)
+++ client/views/pages/dbConnection/InsertDBConnection.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/gisinfo/GisInfoInsert.vue (added)
+++ client/views/pages/gisinfo/GisInfoInsert.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/gisinfo/GisInfoList.vue (added)
+++ client/views/pages/gisinfo/GisInfoList.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/gisinfo/GisInfoSelectListOne.vue (added)
+++ client/views/pages/gisinfo/GisInfoSelectListOne.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/integration/DBConnectionList.vue (added)
+++ client/views/pages/integration/DBConnectionList.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/integration/DepartmentManagement.vue (added)
+++ client/views/pages/integration/DepartmentManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/integration/InsertDBConnection.vue (added)
+++ client/views/pages/integration/InsertDBConnection.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/integration/UserManagement.vue (added)
+++ client/views/pages/integration/UserManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/login/Login.vue (added)
+++ client/views/pages/login/Login.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/main/Main.vue (added)
+++ client/views/pages/main/Main.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/meta/DataMetaManagement.vue (added)
+++ client/views/pages/meta/DataMetaManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/meta/TermManagement.vue (added)
+++ client/views/pages/meta/TermManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/openapi/OpenApiInsert.vue (added)
+++ client/views/pages/openapi/OpenApiInsert.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/openapi/OpenApiKeyList.vue (added)
+++ client/views/pages/openapi/OpenApiKeyList.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/openapi/OpenApiList.vue (added)
+++ client/views/pages/openapi/OpenApiList.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/openapi/OpenApiSelectListOne.vue (added)
+++ client/views/pages/openapi/OpenApiSelectListOne.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/push/Push.vue (added)
+++ client/views/pages/push/Push.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/push/service-worker.js (added)
+++ client/views/pages/push/service-worker.js
This diff is skipped because there are too many other diffs.
 
client/views/pages/schedule/CustomNode.vue (added)
+++ client/views/pages/schedule/CustomNode.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/schedule/ScheduleLogManagement.vue (added)
+++ client/views/pages/schedule/ScheduleLogManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/schedule/ScheduleManagement.vue (added)
+++ client/views/pages/schedule/ScheduleManagement.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/schedule/VueFlowZone.vue (added)
+++ client/views/pages/schedule/VueFlowZone.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/user/myPage.vue (added)
+++ client/views/pages/user/myPage.vue
This diff is skipped because there are too many other diffs.
 
client/views/pages/user/myPagePwd.vue (added)
+++ client/views/pages/user/myPagePwd.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/guide/TemplateGuide.vue (added)
+++ client/views/template/guide/TemplateGuide.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/layoutTemplate/Horizontal.vue (added)
+++ client/views/template/layoutTemplate/Horizontal.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/layoutTemplate/Vertical.vue (added)
+++ client/views/template/layoutTemplate/Vertical.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/AlertModal.vue (added)
+++ client/views/template/templateElement/AlertModal.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/BtnPosition.vue (added)
+++ client/views/template/templateElement/BtnPosition.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/BtnTable.vue (added)
+++ client/views/template/templateElement/BtnTable.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/DefaultSearchBar.vue (added)
+++ client/views/template/templateElement/DefaultSearchBar.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/DetailSearchBar.vue (added)
+++ client/views/template/templateElement/DetailSearchBar.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/FormModal.vue (added)
+++ client/views/template/templateElement/FormModal.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/FormTable.vue (added)
+++ client/views/template/templateElement/FormTable.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/FormTable2.vue (added)
+++ client/views/template/templateElement/FormTable2.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/Icon.vue (added)
+++ client/views/template/templateElement/Icon.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/ListModal.vue (added)
+++ client/views/template/templateElement/ListModal.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/ListTable.vue (added)
+++ client/views/template/templateElement/ListTable.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/Searchbar.vue (added)
+++ client/views/template/templateElement/Searchbar.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/StickyListTable.vue (added)
+++ client/views/template/templateElement/StickyListTable.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/Table.vue (added)
+++ client/views/template/templateElement/Table.vue
This diff is skipped because there are too many other diffs.
 
client/views/template/templateElement/Test.vue (added)
+++ client/views/template/templateElement/Test.vue
This diff is skipped because there are too many other diffs.
 
package-lock.json (added)
+++ package-lock.json
This diff is skipped because there are too many other diffs.
 
package.json (added)
+++ package.json
This diff is skipped because there are too many other diffs.
 
server/modules/db/mysql/MysqlConnection.js (added)
+++ server/modules/db/mysql/MysqlConnection.js
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/OracleConnection.js (added)
+++ server/modules/db/oracle/OracleConnection.js
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_19.16/BASIC_LICENSE (added)
+++ server/modules/db/oracle/client/client_19.16/BASIC_LICENSE
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_19.16/BASIC_README (added)
+++ server/modules/db/oracle/client/client_19.16/BASIC_README
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_19.16/adrci.exe (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/adrci.exe
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/adrci.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/adrci.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/genezi.exe (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/genezi.exe
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/genezi.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/genezi.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oci.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oci.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oci.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oci.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/ocijdbc19.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/ocijdbc19.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/ocijdbc19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/ocijdbc19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/ociw32.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/ociw32.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/ociw32.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/ociw32.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/ojdbc8.jar (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/ojdbc8.jar
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oramysql19.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oramysql19.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oramysql19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oramysql19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/orannzsbb19.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/orannzsbb19.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/orannzsbb19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/orannzsbb19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oraocci19.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oraocci19.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oraocci19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oraocci19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oraocci19d.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oraocci19d.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oraocci19d.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oraocci19d.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oraociei19.dll (added)
+++ server/modules/db/oracle/client/client_19.16/oraociei19.dll
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_19.16/oraociei19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oraociei19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/oraons.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/oraons.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/orasql19.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/orasql19.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/orasql19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/orasql19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/ucp.jar (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/ucp.jar
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/uidrvci.exe (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/uidrvci.exe
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/uidrvci.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/uidrvci.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/vc14/oraocci19.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/vc14/oraocci19.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/vc14/oraocci19.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/vc14/oraocci19.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/vc14/oraocci19d.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/vc14/oraocci19d.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/vc14/oraocci19d.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/vc14/oraocci19d.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_19.16/xstreams.jar (Binary) (added)
+++ server/modules/db/oracle/client/client_19.16/xstreams.jar
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/BASIC_LICENSE (added)
+++ server/modules/db/oracle/client/client_21.6/BASIC_LICENSE
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_21.6/BASIC_README (added)
+++ server/modules/db/oracle/client/client_21.6/BASIC_README
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_21.6/adrci.exe (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/adrci.exe
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/adrci.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/adrci.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/genezi.exe (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/genezi.exe
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/genezi.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/genezi.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/network/admin/README (added)
+++ server/modules/db/oracle/client/client_21.6/network/admin/README
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_21.6/oci.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oci.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oci.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oci.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/ocijdbc21.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/ocijdbc21.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/ocijdbc21.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/ocijdbc21.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/ociw32.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/ociw32.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/ociw32.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/ociw32.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/ojdbc8.jar (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/ojdbc8.jar
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oramysql.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oramysql.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oramysql.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oramysql.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/orannzsbb.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/orannzsbb.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/orannzsbb.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/orannzsbb.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oraocci21.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oraocci21.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oraocci21.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oraocci21.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oraocci21d.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oraocci21d.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oraocci21d.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oraocci21d.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/oraociei.dll (added)
+++ server/modules/db/oracle/client/client_21.6/oraociei.dll
This diff is skipped because there are too many other diffs.
 
server/modules/db/oracle/client/client_21.6/oraociei.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/oraociei.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/orasql.dll (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/orasql.dll
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/orasql.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/orasql.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/ucp.jar (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/ucp.jar
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/uidrvci.exe (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/uidrvci.exe
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/uidrvci.sym (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/uidrvci.sym
Binary file is not shown
 
server/modules/db/oracle/client/client_21.6/xstreams.jar (Binary) (added)
+++ server/modules/db/oracle/client/client_21.6/xstreams.jar
Binary file is not shown
 
server/modules/db/postgresql/PostgresqlConnection.js (added)
+++ server/modules/db/postgresql/PostgresqlConnection.js
This diff is skipped because there are too many other diffs.
 
server/modules/log/Logger.js (added)
+++ server/modules/log/Logger.js
This diff is skipped because there are too many other diffs.
 
server/modules/util/Queue.js (added)
+++ server/modules/util/Queue.js
This diff is skipped because there are too many other diffs.
 
server/modules/web/2024020798270.crt.pem (added)
+++ server/modules/web/2024020798270.crt.pem
This diff is skipped because there are too many other diffs.
 
server/modules/web/2024020798270.key.pem (added)
+++ server/modules/web/2024020798270.key.pem
This diff is skipped because there are too many other diffs.
 
server/modules/web/server.js (added)
+++ server/modules/web/server.js
This diff is skipped because there are too many other diffs.
 
server/service/test/model/TestDAO.js (added)
+++ server/service/test/model/TestDAO.js
This diff is skipped because there are too many other diffs.
 
server/service/test/model/TestService.js (added)
+++ server/service/test/model/TestService.js
This diff is skipped because there are too many other diffs.
 
server/service/test/router/TestRouter.js (added)
+++ server/service/test/router/TestRouter.js
This diff is skipped because there are too many other diffs.
 
vetur.config.js (added)
+++ vetur.config.js
This diff is skipped because there are too many other diffs.
 
webpack.config.js (added)
+++ webpack.config.js
This diff is skipped because there are too many other diffs.
Add a comment
List