박정하 박정하 02-28
250228 박정하 데이터 활용관리 레이아웃 수정
@8a147d5254689128273604d0205f7654e58610f8
client/resources/css/style.css
--- client/resources/css/style.css
+++ client/resources/css/style.css
@@ -63,18 +63,8 @@
     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 {
+    display: block;
     width: 100%;
     height: 200px;
     text-align: center;
@@ -152,7 +142,8 @@
 }
 
 .data-list {
-    height: calc(100% - 47px);
+    height: 100%;
+    max-height: 310px;
     overflow-y: auto;
     background-color: #f8f8f8;
     border-radius: 5px;
 
client/views/component/LayoutTree.vue (deleted)
--- client/views/component/LayoutTree.vue
@@ -1,116 +0,0 @@
-<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/SplitterLayout.vue
--- client/views/component/SplitterLayout.vue
+++ client/views/component/SplitterLayout.vue
@@ -1,131 +1,42 @@
 <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> -->
+  <Splitter :class="{ 'content-box': true, active: splitInfo.layout_nm == selectLayout.layout_nm }" :layout="splitInfo.layout_type" @resizeend="updateSizes" @Click.stop="clickLayout(splitInfo)">
+    <SplitterPanel class="content-box" v-if="splitInfo.children.length < 1">
+      <ComponentTitle v-if="splitInfo.useSj" :title="splitInfo.layoutSj" />
+      <BaseComponent v-else-if="splitInfo.se == 'component'" :component="splitInfo.component" @onChange="onChange" @Click.stop="clickLayout(splitInfo)" />
     </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>
+    <template v-else>
+      <SplitterPanel class="content-box" v-for="(item, idx) of splitInfo.children" :key="idx">
+        <SplitterLayout :splitInfo="item" :selectLayout="selectLayout" @onChange="onChange" />
+      </SplitterPanel>
+    </template>
   </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
+import BaseComponent from "./elementComponent/BaseComponent.vue";
+import ComponentTitle from './elementComponent/ComponentTitle.vue';
 
 export default {
   name: "SplitterLayout",
+  components: { BaseComponent, ComponentTitle },
   props: {
     splitInfo: {
       type: Object,
     },
-    clickElement: null,
-    optionChangeClick: {
-      type: Function,
-    },
-    componentOptn: {
+    selectLayout: {
       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"];
+      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("onChange", splitInfo);
     },
-  },
-  computed: {
-    isReSize() {
-      return this.$route.path.includes("customSelectOne") ? false : true;
+    onChange: function (layout) {
+      this.$emit("onChange", layout);
     },
   },
 };
@@ -134,11 +45,8 @@
 .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 (deleted)
--- client/views/component/SplitterLayoutDev.vue
@@ -1,173 +0,0 @@
-<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/connection/itm/fileSelect.vue
--- client/views/component/connection/itm/fileSelect.vue
+++ client/views/component/connection/itm/fileSelect.vue
@@ -424,7 +424,7 @@
 
 <script>
 import axios from "axios";
-import TreeItem from "../../FileTree.vue";
+import TreeItem from "../../treeMenu/FileTree.vue";
 import SvgIcon from "@jamescoyle/vue-icon";
 import FileDataRead from "./fileDataRead.vue";
 import {
client/views/component/elementComponent/BaseComponent.vue
--- client/views/component/elementComponent/BaseComponent.vue
+++ client/views/component/elementComponent/BaseComponent.vue
@@ -1,70 +1,41 @@
-
 <template>
-    <div style="width:100%;height:100%">       
-        <div style="height: 100px;" >
-            <!--  -->
-            <ComponentTitle :title = baseComponent.sj v-if="component.sj_at"/>
-        </div>
-        <div style="height: calc(100% - 100px);">
-            <component :is="baseComponent.component_itm.component_nm" :createDataObj="baseComponent.component_itm" ></component>
-        </div>
-        
-    </div>
+  <component :is="baseComponent.component_itm.component_nm" :createDataObj="baseComponent.component_itm" />
 </template>
-
-
 <script>
-
-import ComponentTitle from './ComponentTitle.vue';
 import ChartCmmn from '../chart/ChartCmmn.vue';
 import ColAndLine from '../chart/ColAndLine.vue';
 import chartDataTransform from '../../component/chart/chartDataTransform.js';
 
 export default {
-    props: {
-        component: {
-            type: Object,
-            default: null
-        },
+  props: {
+    component: {
+      type: Object,
+      default: null
     },
-    data() {
-        return {
-            baseComponent : this.component,
-
-        }
-    },
-    methods: {
-
-    },
-    watch: {
-        component : {
-            handler : function(){
-                this.baseComponent = this.component;
-            },
-            deep : true
-        }
-        
-    },
-    computed: {
-
-
-
-
-         
-    },
-    components: {       
-       'ComponentTitle' : ComponentTitle,
-       'ChartCmmn' : ChartCmmn,
-       'ColAndLine' : ColAndLine
-    },
-    mounted() {
-        let changeData = this.component.component_itm;
-        let rowData = changeData.dataTable.rowData;
-
-        if(this.component.component_itm.data_list == null){
-            this.component.component_itm.data_list = chartDataTransform.createData(rowData, changeData.categoryAxis, changeData.valueAxis, "null");
-        }
-    },
+  },
+  data() {
+    return {
+      baseComponent: this.component,
+    }
+  },
+  watch: {
+    component: {
+      handler: function () {
+        this.baseComponent = this.component;
+      },
+      deep: true
+    }
+  },
+  components: {
+    'ChartCmmn': ChartCmmn,
+    'ColAndLine': ColAndLine
+  },
+  mounted() {
+    let changeData = this.component.component_itm;
+    let rowData = changeData.dataTable.rowData;
+    if (this.component.component_itm.data_list == null) {
+      this.component.component_itm.data_list = chartDataTransform.createData(rowData, changeData.categoryAxis, changeData.valueAxis, "null");
+    }
+  },
 }
-
-</script>
+</script>
(파일 끝에 줄바꿈 문자 없음)
client/views/component/elementComponent/ComponentTitle.vue
--- client/views/component/elementComponent/ComponentTitle.vue
+++ client/views/component/elementComponent/ComponentTitle.vue
@@ -1,48 +1,26 @@
-
 <template>
-    <div>
-        <div class="component-title-zone mb10">
-            <span class="component-maintitle" :style="this.$createStyleSheet(title.styleSheetMain)">
-                {{title.main_sj}}
-            </span>
-            <span class="component-subtitle" :style="this.$createStyleSheet(title.styleSheetSub)">
-                {{title.sub_sj}}              
-            </span>
-        </div>
-    </div>
+  <div class="component-title-zone mb10">
+    <span class="component-maintitle" :style="this.$createStyleSheet(title.styleSheetMain)">{{ title.main_sj }}</span>
+    <span class="component-subtitle" :style="this.$createStyleSheet(title.styleSheetSub)">{{ title.sub_sj }}</span>
+  </div>
 </template>
-
 <script>
 export default {
-    props: {       
-        title: {
-            type: Object,
-            default: null
-        },
+  props: {
+    title: {
+      type: Object,
+      default: null
     },
-    data() {
-        return {
-            titleComponent : this.title         
-        }
-    },
-    methods: {
-       
-
-    },
-    watch: {
-        component : function(v){
-            this.baseComponent = v;
-        }
-    },
-    computed: {
-
-    },
-    components: {       
-       
-    },
-    mounted() {
-      
-    },
+  },
+  data() {
+    return {
+      titleComponent: this.title
+    }
+  },
+  watch: {
+    component: function (v) {
+      this.baseComponent = v;
+    }
+  },
 }
-
-</script>
+</script>
(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/flowComponent/DataComponent.vue (added)
+++ client/views/component/flowComponent/DataComponent.vue
@@ -0,0 +1,458 @@
+<template>
+  <!-- 데이터 관리 -->
+  <div class="data-set" style="border-bottom: 1px solid #ddd">
+    <div class="content-box flex-column pd10">
+      <div class="content-titleZone">
+        <div class="flex justify-between align-center">
+          <p class="box-title">데이터 목록</p>
+          <button
+            type="button"
+            class="blue-border-btn small-btn"
+            @click="fnCreateJobGroup"
+          >
+            데이터 추가
+          </button>
+        </div>
+      </div>
+      <div class="data-list">
+        <div
+          v-for="(item, idx) of jobGroupList"
+          :key="idx"
+          @click="fnSelectJobGroup(item)"
+        >
+          <div
+            :class="{
+              'item flex justify-between align-center': true,
+              mb5: idx < jobGroupList.length - 1,
+              selectData: item.group_nm == currentJobGroup.group_nm,
+            }"
+          >
+            <p class="flex align-center">
+              <svg-icon
+                type="mdi"
+                class="mr5"
+                :width="20"
+                :height="20"
+                :path="dataPath"
+              ></svg-icon>
+              <span>{{ item.group_nm }}</span>
+            </p>
+            <div>
+              <button
+                class="blue-border-btn set-btn"
+                @click.stop=""
+                @click="fnModalOpen(item, idx)"
+              >
+                설정
+              </button>
+              <button
+                class="red-border-btn set-btn"
+                @click.stop=""
+                @click="deleteDiagram(item)"
+              >
+                삭제
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- 컬럼 정보 -->
+  <div class="column-list flex no-gutter">
+    <div class="content-box flex50 pd10" style="border-right: 1px solid #ddd">
+      <div class="flex100 content-box">
+        <p class="box-title mb10">컬럼정보</p>
+        <div class="overflow-y" style="height: calc(100% - 22px)">
+          <template v-if="columns.length > 0">
+            <div
+              v-for="(column, idx) of columns"
+              :key="idx"
+              :class="{ item: true, mb5: idx < columns.length - 1 }"
+              draggable="true"
+              @dragstart="startDrag($event, column, idx)"
+            >
+              <p>
+                <b>{{ column.displyColumnNm }}</b>
+                ({{ column.columnNm }}) : [{{ column.dataTy }}]
+              </p>
+            </div>
+          </template>
+          <div v-else>
+            <p style="font-size: 1.3rem">컬럼 데이터가 존재하지 않습니다.</p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="content-box flex50">
+      <div class="content-box flex-column no-gutter pd10 overflow-y">
+        <div class="flex30 mb10">
+          <div class="editor-box content-box pd10">
+            <p class="object-title mb5">항목(Category)</p>
+            <div
+              class="overflow-y"
+              style="height: calc(100% - 22px)"
+              @drop.prevent="onDrop($event, 'xdata')"
+              @dragenter.prevent
+              @dragover.prevent
+            >
+              <template v-if="dataX.length > 0">
+                <div
+                  v-for="(column, indx) of dataX"
+                  :key="indx"
+                  :class="{
+                    'item flex justify-between align-center': true,
+                    mb5: idx < dataX.length - 1,
+                  }"
+                >
+                  <p>{{ column.displyColumnNm }} : [{{ column.dataTy }}]</p>
+                  <div>
+                    <button
+                      class="red-border-btn set-btn"
+                      @click="deleteItem(column, 'x')"
+                    >
+                      삭제
+                    </button>
+                  </div>
+                </div>
+              </template>
+              <div class="item mb5" v-else>
+                <p>NO_DATA</p>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="flex40 mb10">
+          <div class="editor-box content-box pd10">
+            <div class="flex justify-between align-center">
+              <p class="object-title mb5">값(Value)</p>
+              <select v-model="currentCalc">
+                <option
+                  v-for="(item, idx) of calcList"
+                  :key="idx"
+                  :value="item.key"
+                >
+                  {{ item.value }}
+                </option>
+              </select>
+            </div>
+            <div
+              class="overflow-y"
+              style="height: calc(100% - 22px)"
+              @drop.prevent="onDrop($event, 'ydata')"
+              @dragenter.prevent
+              @dragover.prevent
+            >
+              <template v-if="dataY.length > 0">
+                <div
+                  v-for="(column, indx) of dataY"
+                  :key="indx"
+                  :class="{
+                    'item flex justify-between align-center': true,
+                    mb5: idx < dataY.length - 1,
+                  }"
+                >
+                  <p>{{ column.displyColumnNm }} : [{{ column.dataTy }}]</p>
+                  <button
+                    class="red-border-btn set-btn"
+                    @click="deleteItem(column, 'y')"
+                  >
+                    삭제
+                  </button>
+                </div>
+              </template>
+              <div class="item mb5" v-else>
+                <p>NO_DATA</p>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="flex20 mb10">
+          <div class="editor-box content-box pd10">
+            <p class="object-title mb5">다음으로 색상 지정</p>
+            <ul class="overflow-y" style="height: calc(100% - 16px)">
+              <li class="item mb5">
+                <p>column</p>
+              </li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- 잡그룹관리 모달 -->
+  <div class="modal-wrapper" v-if="isModalOpen">
+    <div class="modal-container">
+      <div class="modal-title flex justify-between align-center">
+        <h2>잡그룹 관리</h2>
+        <button class="close-btn" @click="fnModalClose">
+          <svg-icon
+            type="mdi"
+            :width="20"
+            :height="20"
+            :path="closePath"
+          ></svg-icon>
+        </button>
+      </div>
+      <div class="modal-content-monthly mb10">
+        <JobGroupManagement :jobGroup="currentJobGroup" />
+      </div>
+      <div class="modal-end flex justify-end">
+        <button class="blue-btn small-btn" @click="fnSaveJobGroup">저장</button>
+        <button class="blue-border-btn small-btn" @click="fnModalClose">
+          취소
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+// icon
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiDatabase, mdiClose } from "@mdi/js";
+import JobGroupManagement from "./JobGroupManagement.vue";
+
+export default {
+  components: {
+    SvgIcon,
+    JobGroupManagement,
+  },
+
+  props: {
+    currentJobGroupList: {
+      type: Array,
+      default: [],
+    },
+    layoutChartData: {
+      type: Object,
+      default: {},
+    },
+    splitInfo: {
+      type: Object,
+      default: {},
+    },
+  },
+
+  data() {
+    return {
+      // icon
+      closePath: mdiClose,
+      dataPath: mdiDatabase,
+
+      dragData: {},
+      dataX: [], // x축 데이터 리스트
+      dataY: [], // y축 데이터 리스트
+
+      // 잡그룹목록
+      jobGroupList: [],
+
+      // 잡그룹관리 모달
+      isModalOpen: false,
+      currentJobGroup: Object.assign({}, this.$getDefaultObject().jobGroup),
+      currentJobGroupIdx: null,
+
+      // 컬럼정보
+      columns: [],
+      currentCalc: "default",
+      calcList: [
+        { key: "default", value: "기본" },
+        { key: "sum", value: "합계" },
+        { key: "avg", value: "평균" },
+        { key: "min", value: "최솟값" },
+        { key: "max", value: "최댓값" },
+      ],
+    };
+  },
+
+  watch: {
+    currentJobGroupList: {
+      handler(value) {
+        this.jobGroupList = value;
+      },
+      deep: true,
+    },
+
+    layoutChartData: {
+      handler() {
+        if (this.layoutChartData.component != null) {
+          this.dataX =
+            this.layoutChartData.component.component_itm.categoryAxis;
+          this.dataY = this.layoutChartData.component.component_itm.valueAxis;
+          this.currentCalc =
+            this.layoutChartData.component.component_itm.chart_cal;
+        } else {
+          this.dataX = [];
+          this.dataY = [];
+          this.currentCalc = "default";
+        }
+      },
+      deep: true,
+    },
+
+    currentCalc: function (v) {
+      if (this.layoutChartData.component != null) {
+        this.$emit("currentCalc", v);
+      }
+    },
+  },
+
+  methods: {
+    // 잡그룹 추가
+    fnCreateJobGroup() {
+      let jobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
+      jobGroup.group_nm = "데이터" + (this.jobGroupList.length + 1);
+
+      this.jobGroupList.push(jobGroup);
+    },
+
+    // 잡그룹 선택
+    fnSelectJobGroup(jobGroup) {
+      this.currentJobGroup = _.cloneDeep(jobGroup);
+      this.fnUpdateColumns();
+    },
+
+    // 잡그룹관리 모달 열기
+    fnModalOpen(jobGroup, idx) {
+      this.currentJobGroup = _.cloneDeep(jobGroup);
+      this.currentJobGroupIdx = idx;
+
+      this.isModalOpen = true;
+    },
+
+    // 잡그룹관리 모달 닫기
+    fnModalClose() {
+      this.isModalOpen = false;
+    },
+
+    // 잡그룹관리 저장
+    fnSaveJobGroup() {
+      let result = this.currentJobGroup;
+      this.jobGroupList[this.currentJobGroupIdx] = result;
+      this.fnUpdateColumns(); // 컬럼정보 갱신
+
+      this.fnModalClose(); // 노드설정 모달 닫기
+    },
+
+    // 컬럼정보 갱신
+    fnUpdateColumns() {
+      const index = this.currentJobGroup.jobItms.length - 1;
+      const dataTable = this.currentJobGroup.jobItms[index].dataTable;
+
+      this.columns = []; // 초기화
+      if (!this.$isEmpty(dataTable)) {
+        this.columns = dataTable.columnDatas;
+      }
+
+      this.$emit("onSelect", this.currentJobGroup);
+    },
+
+    // 드래그 이벤트
+    startDrag(event, data, idx) {
+      this.dragData = data;
+    },
+
+    // 드랍 이벤트
+    async onDrop(event, type) {
+      if (type == "xdata") {
+        if (this.dataX.length > 0) {
+          let isCheck = await this.$showConfirm(
+            "경고",
+            "데이터를 변경하시겠습니까?"
+          );
+          if (!isCheck) {
+            return;
+          }
+        }
+        this.dataX = []; // 초기화
+        this.dataX.push(this.dragData);
+      } else if (type == "ydata") {
+        if (this.dragData.dataTy === "STRING") {
+          this.$showAlert(
+            "메세지",
+            "값(Value)에는 숫자 데이터만 사용 가능합니다."
+          );
+          return;
+        }
+        this.dataY.push(this.dragData);
+      }
+
+      this.$emit("axisData", { dataX: this.dataX, dataY: this.dataY });
+    },
+
+    // 아이템 삭제
+    deleteItem(column, category) {
+      if (category == "x") {
+        for (let i = 0; i < this.dataX.length; i++) {
+          if (this.dataX[i].orginlColumnNm == column.orginlColumnNm) {
+            this.dataX.splice(i, 1);
+          }
+        }
+      } else if (category == "y") {
+        for (let i = 0; i < this.dataY.length; i++) {
+          if (this.dataY[i].orginlColumnNm == column.orginlColumnNm) {
+            this.dataY.splice(i, 1);
+          }
+        }
+      }
+    },
+
+    // 잡그룹 삭제
+    async deleteDiagram(item) {
+      let isCheck = await this.$showConfirm(
+        "삭제",
+        "선택한 데이터를 삭제하시겠습니까?\n관련된 데이터도 함께 삭제됩니다."
+      );
+      if (!isCheck) {
+        return;
+      }
+
+      // splitInfo에 적용했던 데이터가 있는지 확인하여 삭제 (재귀)
+      if (this.splitInfo.children.length > 0) {
+        this.RecursiveFunc(this.splitInfo.children, item);
+      }
+
+      if (this.splitInfo.component != null) {
+        if (this.splitInfo.component.jobInfo[0].group_nm == item.group_nm) {
+          this.splitInfo.component = null;
+          this.splitInfo.se = "splitter";
+        }
+      }
+
+      this.jobGroupList = this.jobGroupList.filter(
+        (jobItem) => jobItem !== item
+      );
+
+      // 초기화
+      this.dragData = {};
+      this.dataX = [];
+      this.dataY = [];
+      this.columns = [];
+    },
+
+    RecursiveFunc(children, item) {
+      // 재귀함수
+      for (let i = 0; i < children.length; i++) {
+        if (children[i].children.length > 0) {
+          this.RecursiveFunc(children[i].children, item);
+        } else {
+          if (children[i].component != null) {
+            // children[i].jobInfo 와 item이 같은지 확인하여 삭제
+            if (children[i].component.jobInfo[0].group_nm == item.group_nm) {
+              children[i].component = null;
+              children[i].se = "splitter";
+            }
+          }
+        }
+      }
+    },
+  },
+};
+</script>
+
+<style scoped>
+.selectData {
+  border: solid 2px #5d5c5c;
+  color: #ffffff;
+  background-color: #ff9e29;
+}
+</style>
 
client/views/component/flowComponent/JobGroupManagement.vue (added)
+++ client/views/component/flowComponent/JobGroupManagement.vue
@@ -0,0 +1,252 @@
+<template>
+  <div class="flex-column">
+    <div class="content-titleZone flex justify-between align-center">
+      <p class="box-title">잡그룹 목록</p>
+      <button class="blue-border-btn small-btn" @click="fnOpenModal">
+        아이템 추가
+      </button>
+    </div>
+    <div class="data-list">
+      <template v-for="(item, idx) in currentJobGroup.jobItms" :key="idx">
+        <div
+          :class="{
+            node: true,
+            mb5: idx < currentJobGroup.jobItms.length - 1,
+          }"
+        >
+          <div class="node-body flex justify-between align-center">
+            <div class="flex align-center">
+              <p class="box-title">
+                {{ idx + 1 }}. {{ viewChanageByType(item.type) }}
+              </p>
+              <p class="ml10">
+                ({{
+                  item.dataTable.columnDatas.length > 0
+                    ? "설정 완료"
+                    : "미설정"
+                }})
+              </p>
+            </div>
+            <div>
+              <button
+                type="button"
+                class="blue-border-btn set-btn"
+                @click="fnOpenSetup(item, idx)"
+              >
+                수정
+              </button>
+              <button
+                type="button"
+                class="red-border-btn set-btn"
+                @click="fnDeleteItem(item)"
+              >
+                삭제
+              </button>
+            </div>
+          </div>
+        </div>
+      </template>
+    </div>
+  </div>
+  <!-- 잡아이템 추가 모달 -->
+  <CreateNodeModal
+    v-if="isOpenModal"
+    page="jobGroup"
+    @addNode="fnAddNode"
+    @closeModal="fnCloseModal"
+  />
+  <!-- 잡아이템 셋업 모달 -->
+  <NodeSetupModal
+    v-if="isOpenSetupModal"
+    :frontNodes="frontNodes"
+    :currentJobItm="currentJobItm"
+    @onSave="fnSaveSetup"
+    @onClose="fnCloseSetup"
+  />
+</template>
+
+<script>
+// icon
+import SvgIcon from "@jamescoyle/vue-icon";
+import { mdiClose, mdiTrashCan, mdiCog } from "@mdi/js";
+// modal
+import CreateNodeModal from "../modal/CreateNodeModal.vue";
+import NodeSetupModal from "../modal/NodeSetupModal.vue";
+
+export default {
+  components: {
+    // icon
+    SvgIcon,
+    // modal
+    CreateNodeModal,
+    NodeSetupModal,
+  },
+
+  props: {
+    jobGroup: {
+      type: Object,
+      default: {},
+    },
+  },
+
+  data() {
+    return {
+      // icon
+      closePath: mdiClose,
+
+      selectItem: "DATASET_READ", // 노드 추가값
+      isSetupModalOpen: false, // 노드 설정 모달 열림 여부(true:열림/false:닫힘)
+      nodeTypes: [
+        { id: "DB_READ", name: "DB 조회" },
+        { id: "DATASET_READ", name: "데이터셋 조회" },
+        { id: "DATA_FILTER", name: "데이터 필터" },
+      ],
+      selectNode: {}, // 선택된 아이템
+      selectNodeIndex: null, // 선택된 아이템 인덱스
+
+      defaultJobGroup: Object,
+      defaultJobItm: Object,
+      delPath: mdiTrashCan,
+      setPath: mdiCog,
+
+      currentJobGroup: this.jobGroup,
+
+      // 아이템 추가 모달
+      isOpenModal: false,
+      frontNodes: [],
+      currentJobItm: {},
+      currentJobItmIdx: null,
+
+      // 잡아이템 세팅 모달
+      isOpenSetupModal: false,
+    };
+  },
+
+  created() {},
+
+  mounted() {},
+
+  watch: {
+    jobGroup: {
+      handler(value) {
+        this.currentJobGroup = value;
+      },
+      deep: true,
+    },
+  },
+
+  methods: {
+    // 아이템추가 모달 열기
+    fnOpenModal() {
+      this.isOpenModal = true;
+    },
+
+    // 아이템추가 모달 닫기
+    fnCloseModal() {
+      this.isOpenModal = false;
+    },
+
+    // 잡아이템 추가
+    fnAddNode(item) {
+      let type = this.fnFindJobItemTypeGroup(item);
+      if (type == "READ") {
+        for (let jobItem of this.currentJobGroup.jobItms) {
+          let jobType = this.fnFindJobItemTypeGroup(jobItem);
+          if (jobType == "READ") {
+            this.$showAlert(
+              "경고",
+              "읽기 아이템은 2개 이상 추가할 수 없습니다."
+            );
+            return;
+          }
+        }
+      }
+
+      this.currentJobGroup.jobItms.push(item.jobItm);
+    },
+
+    // 잡아이템 타입 분류 찾기
+    fnFindJobItemTypeGroup(jobItem) {
+      let typeArr = jobItem.type.split("_");
+      return typeArr[typeArr.length - 1].toUpperCase();
+    },
+
+    // 타입 변환
+    viewChanageByType(type) {
+      let typeArr = type.split("_");
+
+      switch (type) {
+        case "DB_READ":
+        case "API_READ":
+        case "FILE_READ":
+        case "DATASET_READ":
+        case "EHOJO_READ":
+          return typeArr[0] + " 읽기";
+        case "DATA_FILTER":
+          return typeArr[0] + " 필터";
+        // 기본
+        case defalt:
+          return type;
+      }
+    },
+
+    // 잡아이템설정 모달 열기
+    fnOpenSetup(item, idx) {
+      this.currentJobItm = item;
+      this.currentJobItmIdx = idx;
+
+      let typeArr = item.type.split("_");
+      if (typeArr[typeArr.length - 1] == "FILTER") {
+        this.frontNodes = []; // 초기화
+        this.frontNodes.push(this.currentJobGroup.jobItms[idx - 1]);
+
+        if (this.frontNodes.length < 1) {
+          this.$showAlert("경고", "선행 노드가 없습니다.");
+          return;
+        }
+      }
+
+      this.isOpenSetupModal = true;
+    },
+
+    // 잡아이템설정 모달 닫기
+    fnCloseSetup() {
+      this.isOpenSetupModal = false;
+
+      // 초기화
+      this.currentJobItm = {};
+      this.currentJobItmIdx = null;
+    },
+
+    // 잡아이템설정 저장
+    fnSaveSetup(jobItem) {
+      this.currentJobGroup.jobItms[this.currentJobItmIdx] = jobItem;
+      this.fnCloseSetup(); // 노드설정 모달 닫기
+    },
+
+    // 잡아이템 삭제
+    async fnDeleteItem(jobItem) {
+      let isCheck = await this.$showConfirm(
+        "경고",
+        "해당 잡아이템을 삭제하시겠습니까?"
+      );
+      if (!isCheck) {
+        return;
+      }
+
+      this.currentJobGroup.jobItms = this.currentJobGroup.jobItms.filter(
+        (item) => item !== jobItem
+      );
+    },
+  },
+};
+</script>
+
+<style scoped>
+.node {
+  background: #ffffff;
+  border: 1px solid #dbe3fb;
+  padding: 10px;
+  border-radius: 10px;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/component/flowComponent/VueFlowZoneGroup.vue (deleted)
--- client/views/component/flowComponent/VueFlowZoneGroup.vue
@@ -1,309 +0,0 @@
-<template>
-  <div class="data-set pt10 pb10" style="border-bottom: 1px solid #ddd">
-    <div class="flex100 content-box">
-      <div class="content-titleZone">
-        <div class="flex justify-between align-center">
-          <p class="box-title">데이터 관리</p>
-          <button class="blue-border-btn small-btn" @click="dataAdd"> 데이터 추가 </button>
-        </div>
-      </div>
-      <div class="data-list">
-        <ul class="flex">
-          <li v-for="(diagram, idx) in diagramList" :key="idx" class="flex50 mb5" @click="clickDiagram(diagram)">
-            <div class="item flex justify-between align-center" :class="{
-              selectData: diagram.group_nm == selectDiagram.group_nm,
-            }">
-              <p class="flex align-center">
-                <svg-icon type="mdi" :path="dataPath"> </svg-icon>
-                <span>{{ diagram.group_nm }}</span>
-              </p>
-              <div>
-                <button class="blue-border-btn set-btn" @click="setModal(diagram)">설정</button>
-                <button class="red-border-btn set-btn" @click="deleteDiagram(diagram)">삭제</button>
-              </div>
-            </div>
-          </li>
-        </ul>
-      </div>
-    </div>
-  </div>
-  <div class="column-list flex no-gutter">
-    <div class="content-box flex50 pt10 pb10" style="border-right: 1px solid #ddd">
-      <div class="flex100 content-box">
-        <p class="box-title mb10">컬럼정보</p>
-        <ul class="overflow-y" style="height: calc(100% - 29px)" v-if="columns != []">
-          <li class="item mb5" v-for="(column, indx) in columns" :key="indx" @dragstart="startDrag($event, column, indx)" draggable="true">
-            <p>{{ column.displyColumnNm }} : [{{ column.dataTy }}]</p>
-          </li>
-        </ul>
-        <ul class="overflow-y" style="height: calc(100% - 29px)" v-else>
-          <li class="item mb5">
-            <p>NO_DATA</p>
-          </li>
-        </ul>
-      </div>
-    </div>
-    <div class="content-box flex50 pt10 pb10">
-      <div class="flex100 content-box flex-column no-gutter overflow-y">
-        <div>
-          <div class="editor-box content-box pd10 mb5" style="display: flex" @drop.prevent="onDrop($event, 'xdata')" @dragenter.prevent @dragover.prevent>
-            <input type="radio" class="selectCal" id="default" value="default" v-model="selectedCal" /> default<br />
-            <input type="radio" class="selectCal" id="sum" value="sum" v-model="selectedCal" /> Sum<br />
-            <input type="radio" class="selectCal" id="avg" value="avg" v-model="selectedCal" /> Average<br />
-            <input type="radio" class="selectCal" id="min" value="min" v-model="selectedCal" /> Min<br />
-            <input type="radio" class="selectCal" id="max" value="max" v-model="selectedCal" /> Max<br />
-          </div>
-        </div>
-        <div class="flex30">
-          <div class="editor-box content-box pd10" @drop.prevent="onDrop($event, 'xdata')" @dragenter.prevent @dragover.prevent>
-            <p class="object-title mb5">수직(Category)</p>
-            <ul class="overflow-y" style="height: calc(100% - 29px)" v-if="dataX.length > 0">
-              <li class="item mb5" v-for="(column, indx) in dataX" :key="indx">
-                <p>{{ column.displyColumnNm }} : [{{ column.dataTy }}]</p>
-                <div>
-                  <button class="red-border-btn set-btn" @click="deleteItem(column, 'x')"> 삭제 </button>
-                </div>
-              </li>
-            </ul>
-            <ul class="overflow-y" style="height: calc(100% - 29px)" v-else>
-              <li class="item mb5">
-                <p>NO_DATA</p>
-              </li>
-            </ul>
-          </div>
-        </div>
-        <div class="flex30 mt10">
-          <div class="editor-box content-box pd10" @drop.prevent="onDrop($event, 'ydata')" @dragenter.prevent @dragover.prevent>
-            <p class="object-title mb5">수평(Value)</p>
-            <ul class="overflow-y" style="height: calc(100% - 29px)" v-if="dataY.length > 0">
-              <li class="item mb5" v-for="(column, indx) in dataY" :key="indx">
-                <p>{{ column.displyColumnNm }} : [{{ column.dataTy }}]</p>
-                <div>
-                  <button class="red-border-btn set-btn" @click="deleteItem(column, 'y')"> 삭제 </button>
-                </div>
-              </li>
-            </ul>
-            <ul class="overflow-y" style="height: calc(100% - 16px)" v-else>
-              <li class="item mb5 orange">
-                <p>column</p>
-              </li>
-            </ul>
-          </div>
-        </div>
-        <div class="flex30 mt10">
-          <div class="editor-box content-box pd10">
-            <p class="object-title mb5">다음으로 색상 지정</p>
-            <ul class="overflow-y" style="height: calc(100% - 16px)">
-              <li class="item mb5">
-                <p>column</p>
-              </li>
-            </ul>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-  <VueFlowZonePopup :isModalOpen="isModalOpen" :diagram="currentDiagram" @updateData="dataUpdate" @close="close" />
-</template>
-<script>
-import { mdiDatabase } from "@mdi/js";
-import VueFlowZonePopup from "./VueFlowZonePopup.vue";
-import TemplateGuide from "../../template/guide/TemplateGuide.vue";
-
-export default {
-  props: {
-    diagramList: {
-      type: Array,
-      default: [],
-    },
-    layoutChartData: {
-      type: Object,
-      default: {},
-    },
-    splitInfo: {
-      type: Object,
-      default: {}
-    }
-  },
-  data() {
-    return {
-      comId:
-        "comId_" +
-        Math.random().toString(36).substring(2, 15) +
-        Math.random().toString(36).substring(2, 15),
-      diagramListModel: [],
-      selectDiagram: _.cloneDeep(this.$getDefaultObject().jobGroup),
-      idIndex: 0,
-      dataPath: mdiDatabase,
-      dragData: {},
-      columns: [], // 데이터 컬럼 정보 리스트 (선택 다이어그램에 따라 변경됨)
-      dataX: [], // x축 데이터 리스트
-      dataY: [], // y축 데이터 리스트
-      selectedCal: "", // 선택된 계산 방법
-
-      isModalOpen: false,
-      currentDiagram: {},
-    };
-  },
-  methods: {
-    // 다이어 그램 추가
-    dataAdd: function () {
-      const temp = _.cloneDeep(this.$getDefaultObject().jobGroup);
-      temp.group_nm = "데이터" + ++this.idIndex;
-      this.diagramList.push(temp);
-    },
-
-    close() {
-      this.currentDiagram = {};
-      this.isModalOpen = false;
-    },
-    // 모달 오픈
-    setModal: function (diagram) {
-      this.currentDiagram = diagram;
-      this.isModalOpen = true;
-    },
-
-    // 다이어그램 선택
-    clickDiagram: function (diagram) {
-      // 다이어그램 선택에 따라 컬럼 리스트가 변경되어야함
-      this.selectDiagram = diagram;
-      if (diagram.dataTable != null) {
-        this.columns = diagram.dataTable.columnDatas;
-      } else {
-        this.columns = [];
-      }
-      // 다이어그램 선택에 따른 데이터 테이블 정보를 가져와야함
-      this.$emit("clickDiagramDataTable", diagram);
-    },
-
-    // 데이터 내용 및 선택 데이터 변경시 이벤트
-    dataUpdate: function (diagram) {
-      // diagram 데이터 적용시 가지고오는 데이터 테이블 정보
-      if (diagram.dataTable != null) {
-        this.columns = diagram.dataTable.columnDatas;
-      } else {
-        this.columns = [];
-      }
-      this.$emit("clickDiagramDataTable", diagram);
-    },
-
-    // 드래그 이벤트
-    startDrag: function (event, data, indx) {
-      this.dragData = _.cloneDeep(data);
-      this.dragData.columnIdx = indx;
-    },
-
-    // 드랍 이벤트
-    onDrop: async function (event, idx) {
-      if (idx == "xdata") {
-        if (this.dataX.length > 0) {
-          if (
-            await this.$showConfirm(
-              " Category변경",
-              "Category 데이터를 변경하시겠습니까?"
-            )
-          ) {
-            this.dataX = [];
-            this.dataX.push(_.cloneDeep(this.dragData));
-          }
-        }
-        this.dataX.push(_.cloneDeep(this.dragData));
-      } else if (idx == "ydata") {
-        if (this.dragData.dataTy === "STRING") {
-          this.$showAlert("메세지", "Value에는 숫자 데이터만 사용 가능합니다.");
-          return;
-        }
-        this.dataY.push(_.cloneDeep(this.dragData));
-      }
-
-      this.$emit("axisData", { dataX: this.dataX, dataY: this.dataY });
-    },
-
-    // 아이템 삭제
-    deleteItem: function (column, category) {
-      if (category == "x") {
-        for (let i = 0; i < this.dataX.length; i++) {
-          if (this.dataX[i].orginlColumnNm == column.orginlColumnNm) {
-            this.dataX.splice(i, 1);
-          }
-        }
-      } else if (category == "y") {
-        for (let i = 0; i < this.dataY.length; i++) {
-          if (this.dataY[i].orginlColumnNm == column.orginlColumnNm) {
-            this.dataY.splice(i, 1);
-          }
-        }
-      }
-    },
-    async deleteDiagram(item) {
-      if (await this.$showConfirm("삭제", "선택한 데이터를 삭제하시겠습니까?\n관련된 데이터도 함께 삭제됩니다.")) {
-        const index = this.diagramList.indexOf(item);
-        // splitInfo에 적용했던 데이터가 있는지 확인하여 삭제 (재귀)
-        if (this.splitInfo.children.length > 0) {
-          this.RecursiveFunc(this.splitInfo.children, item);
-        }
-        if (this.splitInfo.component != null) {
-          if (this.splitInfo.component.jobInfo[0].group_nm == item.group_nm) {
-            this.splitInfo.component = null;
-            this.splitInfo.se = "splitter";
-          }
-        }
-        this.diagramList.splice(index, 1);
-        this.columns = [];
-      }
-    },
-    RecursiveFunc: function (children, item) {
-      // 재귀함수
-      for (let i = 0; i < children.length; i++) {
-        if (children[i].children.length > 0) {
-          this.RecursiveFunc(children[i].children, item);
-        }
-        else {
-          if (children[i].component != null) {
-            // children[i].jobInfo 와 item이 같은지 확인하여 삭제
-            if (children[i].component.jobInfo[0].group_nm == item.group_nm) {
-              children[i].component = null;
-              children[i].se = "splitter";
-
-            }
-          }
-        }
-      }
-    }
-  },
-  watch: {
-    diagramList: function (v) {
-      this.idIndex = v.length;
-    },
-    layoutChartData: {
-      handler: function () {
-        if (this.layoutChartData.component != null) {
-          this.dataX = this.layoutChartData.component.component_itm.categoryAxis;
-          this.dataY = this.layoutChartData.component.component_itm.valueAxis;
-          this.selectedCal = this.layoutChartData.component.component_itm.chart_cal;
-        } else {
-          this.dataX = [];
-          this.dataY = [];
-          this.selectedCal = "default";
-        }
-      },
-      deep: true,
-    },
-    selectedCal: function (v) {
-      if (this.layoutChartData.component != null) {
-        this.$emit("selectedCal", v);
-      }
-    },
-  },
-  components: {
-    VueFlowZonePopup: VueFlowZonePopup,
-  },
-};
-</script>
-<style scoped>
-.selectData {
-  border: solid 2px #5d5c5c;
-  color: #ffffff;
-  background-color: #ff9e29;
-}
-</style>
client/views/component/modal/CreateNodeModal.vue
--- client/views/component/modal/CreateNodeModal.vue
+++ client/views/component/modal/CreateNodeModal.vue
@@ -1,9 +1,12 @@
 <template>
   <div class="modal-wrapper">
-    <div class="modal-container" style="width: 500px">
+    <div class="modal-container small-modal">
       <div class="modal-title">
         <div class="flex justify-between align-center">
-          <h2>노드 추가</h2>
+          <h2>
+            <span v-if="page == 'jobGroup'">잡아이템 추가</span>
+            <span v-else>노드 추가</span>
+          </h2>
           <button class="close-btn" @click="fnCloseModal">
             <svg-icon
               type="mdi"
@@ -63,7 +66,12 @@
   props: {
     schdulType: {
       type: String,
-      default: null,
+      default: "DATA_NODE",
+    },
+
+    page: {
+      type: String,
+      default: "schedule",
     },
   },
 
@@ -105,7 +113,13 @@
   methods: {
     // 분류 조회
     async fnSelectTypeList() {
-      this.typeList = await this.$getCommonCodeByTree(this.schdulType);
+      let typeList = await this.$getCommonCodeByTree(this.schdulType);
+      if (this.page == "jobGroup") {
+        this.typeList = [typeList[0], typeList[1]];
+      } else {
+        this.typeList = typeList;
+      }
+
       this.selectedType = this.typeList[0];
       this.selectedNode = this.typeList[0].childList[0];
     },
client/views/component/modal/HostDrctryListModal.vue
--- client/views/component/modal/HostDrctryListModal.vue
+++ client/views/component/modal/HostDrctryListModal.vue
@@ -88,7 +88,7 @@
 import { mdiMagnify, mdiClose } from "@mdi/js";
 // 컴포넌트 import
 import PaginationButton from "../PaginationButton.vue";
-import FileTreeModal from "../FileTreeModal.vue";
+import FileTreeModal from "../treeMenu/FileTreeModal.vue";
 
 export default {
   components: { SvgIcon, PaginationButton, FileTreeModal },
client/views/component/scheduleComponent/nodes/ProcessNode.vue
--- client/views/component/scheduleComponent/nodes/ProcessNode.vue
+++ client/views/component/scheduleComponent/nodes/ProcessNode.vue
@@ -66,23 +66,31 @@
     };
   },
 
+  created() {
+    this.init();
+  },
+
   mounted() {},
 
   watch: {
     "node.jobItm.itm.itemList": {
-      deep: true,
       handler(value) {
-        this.matchCnt = 0;
-        for (let item of value) {
-          if (!this.$isEmpty(item.targetColumnNm)) {
-            this.matchCnt++;
-          }
-        }
+        this.init();
       },
+      deep: true,
     },
   },
 
-  methods: {},
+  methods: {
+    init() {
+      this.matchCnt = 0;
+      for (let item of this.node.jobItm.itm.itemList) {
+        if (!this.$isEmpty(item.targetColumnNm)) {
+          this.matchCnt++;
+        }
+      }
+    },
+  },
 };
 </script>
 
client/views/component/style/StyleSheetComponent.vue
--- client/views/component/style/StyleSheetComponent.vue
+++ client/views/component/style/StyleSheetComponent.vue
@@ -1,15 +1,8 @@
-
 <template>
-  <div>
-    <FontOption v-if="fontAt" />
-    <CellOption :borderStyle="styleSheet.borderStyle" v-if="cellAt" />
-    <BackgroundOption
-      :background_style="styleSheet.background_style"
-      v-if="backgroundAt"
-    />
-  </div>
+  <FontOption v-if="fontAt" />
+  <CellOption :borderStyle="styleSheet.borderStyle" v-if="cellAt" />
+  <BackgroundOption :background_style="styleSheet.background_style" v-if="backgroundAt" />
 </template>
-
 <script>
 import BackgroundOption from "./BackgroundOption.vue";
 import CellOption from "./CellOption.vue";
 
client/views/component/style/TitleStyleComponent.vue (deleted)
--- client/views/component/style/TitleStyleComponent.vue
@@ -1,78 +0,0 @@
-
-<template>
-    <div>
-        <div class="table-zone">
-            <p class="object-title mb5">타이틀 사용 여부</p>
-            <ul>
-                <li class="mb10">
-                    <div class="input-container flex">
-                        <label class="radio-label">
-                            <input type="radio" name="comId+'use_at'" class="custom-radiobox" :value="true" v-model="component.sj_at">
-                            <span>사용</span>
-                        </label>
-                        <label class="radio-label">
-                            <input type="radio" name="comId+'use_at'" class="custom-radiobox" :value="false" v-model="component.sj_at">
-                            <span>사용안함</span>
-                        </label>
-                    </div>
-                </li>
-                
-            </ul>
-            <p class="object-title mb5">메인타이틀</p>
-            <ul>
-                <li class="mb10">
-                    <input type="text" class="full-input" v-model="component.sj.main_sj" />                   
-                </li>
-            </ul>          
-            <FontOption :fontStyle="component.sj.styleSheetMain.fontStyle" :title="'메인 '"/>       
-            <p class="object-title mb5">서브타이틀</p>
-            <ul>
-                <li class="mb10">                    
-                    <input type="text" class="full-input" v-model="component.sj.sub_sj" />
-                </li>
-            </ul>
-             <FontOption :fontStyle="component.sj.styleSheetSub.fontStyle" :title="'서브 '" />        
-        </div>
-    </div>
-</template>
-
-<script>
-import BackgroundOption from './BackgroundOption.vue';
-import CellOption from './CellOption.vue';
-import FontOption from './FontOption.vue';
-
-export default {
-    props: {       
-        component: {
-            type: Object,
-            default: null
-        },       
-    },
-    data() {
-        return {
-           comId: "comId_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),             
-           styleSheetMain : this.component.sj.styleSheetMain,
-           styleSheetSub  : this.component.sj.styleSheetSub       
-        }
-    },
-    methods: {
-       
-
-    },
-    watch: {
-        component : function(v){          
-            this.styleSheetMain = v.sj.styleSheetMain;
-            this.styleSheetSub  = v.sj.styleSheetSub;
-        }
-    },
-    computed: {
-
-    },
-    components: {             
-        'FontOption': FontOption
-    },
-    mounted() {
-    },
-}
-
-</script>
client/views/component/treeMenu/FileTree.vue (Renamed from client/views/component/FileTree.vue)
--- client/views/component/FileTree.vue
+++ client/views/component/treeMenu/FileTree.vue
No changes
client/views/component/treeMenu/FileTreeModal.vue (Renamed from client/views/component/FileTreeModal.vue)
--- client/views/component/FileTreeModal.vue
+++ client/views/component/treeMenu/FileTreeModal.vue
@@ -30,7 +30,7 @@
 <script>
 import axios from 'axios';
 import SvgIcon from '@jamescoyle/vue-icon';
-import TreeItem from '../component/FileTree.vue';
+import TreeItem from './FileTree.vue';
 import { mdiClose } from '@mdi/js';
 export default {
   props: {
 
client/views/component/treeMenu/LayoutTree.vue (added)
+++ client/views/component/treeMenu/LayoutTree.vue
@@ -0,0 +1,83 @@
+<template>
+  <li class="cursor">
+    <div :class="{ 'tree-container flex align-center': true, 'selected': selectLayout.layout_nm === splitInfo.layout_nm }" @click="clickLayout(splitInfo)">
+      <template v-if="splitInfo.children.length > 0">
+        <svg-icon type="mdi" :width="18" :height="18" :path="arrowPath" />
+        <svg-icon type="mdi" :width="18" :height="18" :path="folderPath" :color="'#fbbe28'" />
+      </template>
+      <template v-else>
+        <svg-icon type="mdi" :width="18" :height="18" :path="dotPath" />
+        <svg-icon type="mdi" :width="18" :height="18" :path="layoutPath" :color="'#fbbe28'" />
+      </template>
+      <p class="node-text">{{ splitInfo.layout_nm }}</p>
+    </div>
+    <ul v-if="splitInfo.children.length > 0" class="children-node" :style="{ height: toggleSelect ? 'auto' : '0' }">
+      <TreeItem v-for="(node, indx) in splitInfo.children" :splitInfo="node" :selectLayout='selectLayout' :key="indx" @onChange="onChange" />
+    </ul>
+  </li>
+</template>
+<script>
+import SvgIcon from '@jamescoyle/vue-icon';
+import { mdiLandPlots, mdiChevronRight, mdiRhombusSplit, mdiCircleSmall, mdiCropSquare } from '@mdi/js';
+export default {
+  components: {
+    'SvgIcon': SvgIcon
+  },
+  props: {
+    splitInfo: {
+      type: Object,
+    },
+    selectLayout: {
+      type: Object,
+    }
+  },
+  data() {
+    return {
+      currentSelectLayout: this.selectLayout,
+      toggleSelect: false,
+      clidrunNode: false,
+      folderPath: mdiLandPlots,
+      layoutPath: mdiCropSquare,
+      arrowPath: mdiChevronRight,
+      filePath: mdiRhombusSplit,
+      selectedId: null,
+      parentSelectNode: null,
+      dotPath: mdiCircleSmall,
+    }
+  },
+  watch: {
+    selectLayout(v) {
+      this.currentSelectLayout = v;
+    },
+  },
+  beforeCreate() {
+    this.$options.components.TreeItem = require('./LayoutTree.vue').default;
+  },
+  methods: {
+    clickLayout(splitInfo) {
+      this.toggleSelect = !this.toggleSelect;
+      this.$emit('onChange', splitInfo);
+    },
+
+    onChange(layout) {
+      this.$emit('onChange', layout);
+    },
+  },
+}
+</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/pages/AppRouter.js
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
@@ -28,8 +28,7 @@
 import InsertDBConnection from "../pages/dbConnection/InsertDBConnection.vue";
 import CustomSelectList from "../pages/custom/CustomSelectList.vue";
 import CustomSelectOne from "../pages/custom/CustomSelectOne.vue";
-import CustomInsert from "../pages/custom/CustomInsert.vue";
-import CustomInsertDev from "../pages/custom/CustomInsertDev.vue";
+import InsertDataAnalytics from "./custom/InsertDataAnalytics.vue";
 import OpenApiList from "../pages/openapi/OpenApiList.vue";
 import OpenApiInsert from "../pages/openapi/OpenApiInsert.vue";
 import OpenApiSelectListOne from "../pages/openapi/OpenApiSelectListOne.vue";
@@ -63,7 +62,6 @@
   { path: "/adminManagement.page", name: "AdminManagement", component: AdminManagement }, // 관리자 관리
   { path: "/departmentManagement.page", name: "Department", component: DepartmentManagement }, // 부서 관리
   { path: "/dbConnectionList.page", name: "DBConnectionList", component: DBConnectionList }, // 연계정보 관리
-
   { path: "/fileManagement.page", name: "FileManagement", component: FileManagement },
   { path: "/hostManagement.page", name: "HostManagement", component: HostManagement },
   { path: "/dataManagement.page", name: "DataManagement", component: DataManagement },
@@ -75,10 +73,6 @@
   { path: "/push.page", name: "Push", component: Push },
   { path: "/insertDBConnection.page", name: "InsertDBConnection", component: InsertDBConnection },
   { path: "/DBConnectionDetail.page", name: "DBConnectionDetail", component: DBConnectionDetail },
-  { path: "/customSelectList.page", name: "CustomSelectList", component: CustomSelectList },
-  { path: "/customSelectOne.page", name: "CustomSelectOne", component: CustomSelectOne },
-  { path: "/customInsert.page", name: "CustomInsert", component: CustomInsert },
-  { path: "/customInsertDev.page", name: "CustomInsertDev", component: CustomInsertDev },
   { path: "/openApiList.page", name: "OpenApiList", component: OpenApiList },
   { path: "/openApiInsert.page", name: "OpenApiInsert", component: OpenApiInsert },
   { path: "/openApiListOne.page", name: "OpenApiSelectListOne", component: OpenApiSelectListOne },
@@ -89,6 +83,10 @@
   { path: "/myPage.page", name: "myPage", component: myPage },
   { path: "/myPagePwd.page", name: "myPagePwd", component: myPagePwd },
   { path: "/login.page", name: "Login", component: Login },
+  // 데이터활용관리
+  { path: "/customSelectList.page", name: "CustomSelectList", component: CustomSelectList },
+  { path: "/customSelectOne.page", name: "CustomSelectOne", component: CustomSelectOne },
+  { path: "/insertDataAnalytics.page", name: "InsertDataAnalytics", component: InsertDataAnalytics },
   // 차트
   { path: "/chart.page", name: "Chart", component: Chart },
   // 템플릿 화면
 
client/views/pages/custom/CustomInsert.vue (deleted)
--- client/views/pages/custom/CustomInsert.vue
@@ -1,885 +0,0 @@
-<template>
-  <div class="container">
-    <div class="page-titleZone flex justify-between align-center align-start">
-      <p class="main-title flex80">데이터 현황관리 차트 등록</p>
-      <PageNavigation />
-    </div>
-    <div class="content-wrap content">
-      <div class="row">
-        <div class="custom-info">
-          <details>
-            <summary>데이터 설정</summary>
-            <div class="pd20 detail-content">
-              <JobContainer :jobGroup="jobGroup" @getDataTable="getResult" />
-            </div>
-          </details>
-        </div>
-      </div>
-      <div class="row custom-tab">
-        <div class="content-box flex justify-between align-start">
-          <div
-            :class="{
-              'tab-zone flex align-start': true,
-              flex35: accordion === false,
-              flex5: accordion === true,
-            }"
-          >
-            <div
-              :class="{
-                'column-nav': true,
-                flex10: accordion === false,
-                flex85: accordion === true,
-              }"
-            >
-              <ul>
-                <li
-                  v-for="(tab, idx) in tabMenu"
-                  :key="idx"
-                  @click="showTab(tab.tabNic)"
-                >
-                  <a
-                    :class="{ activeTab: activeTab === tab.tabNic }"
-                    :title="tab.tabName"
-                  >
-                    <svg-icon
-                      type="mdi"
-                      :path="tab.iconPath"
-                      class="mb5"
-                    ></svg-icon>
-                    <p>{{ tab.tabName }}</p>
-                  </a>
-                </li>
-              </ul>
-              <button @click="accordionToggle" class="custom-toggle">
-                <svg-icon
-                  type="mdi"
-                  :path="arrowPath"
-                  :color="'#333'"
-                ></svg-icon>
-              </button>
-            </div>
-            <div
-              class="tab-content flex90"
-              :style="accordion === true ? { display: 'none' } : {}"
-            >
-              <div
-                class="content-box flex justify-between tab-box"
-                v-show="activeTab === 'columnList'"
-              >
-                <div
-                  class="content-list layout flex-column justify-between flex40"
-                >
-                  <div class="page-info mb10">
-                    <div class="content-titleZone">
-                      <div class="flex justify-between align-center">
-                        <p class="box-title">페이지 정보</p>
-                      </div>
-                    </div>
-                    <div>
-                      <div class="input-group mb10">
-                        <label for="" class="mb5">제목</label>
-                        <input
-                          type="text"
-                          name=""
-                          id=""
-                          class="full-input"
-                          @input="customTitle = $event.target.value"
-                        />
-                      </div>
-                      <div class="input-group">
-                        <label for="" class="mb5">설명</label>
-                        <textarea
-                          name=""
-                          id=""
-                          cols="30"
-                          rows="5"
-                          @input="customComment = $event.target.value"
-                        ></textarea>
-                      </div>
-                    </div>
-                  </div>
-                  <div class="layout-tree">
-                    <div class="content-titleZone">
-                      <div class="flex justify-between align-center">
-                        <p class="box-title">레이아웃 옵션</p>
-                        <div>
-                          <button
-                            id="horizontal-btn"
-                            @click="addSplitterLayout('horizontal')"
-                            title="수직레이아웃"
-                          >
-                            <img :src="horizontalImg" />
-                          </button>
-                          <button
-                            id="vertical-btn"
-                            @click="addSplitterLayout('vertical')"
-                            title="수평레이아웃"
-                          >
-                            <img :src="verticalImg" />
-                          </button>
-                        </div>
-                      </div>
-                    </div>
-                    <ul></ul>
-                  </div>
-                </div>
-                <div class="layout-option flex-column justify-between flex60">
-                  <div class="content-titleZone">
-                    <div class="flex justify-between align-center">
-                      <p class="box-title">옵션</p>
-                    </div>
-                  </div>
-                  <div class="modal-content-monthly">
-                    <CellOption />
-                    <BackgroundOption />
-                  </div>
-                  <div class="modal-end flex justify-end">
-                    <button class="gray-border-btn small-btn">취소</button>
-                    <button class="blue-border-btn small-btn">적용</button>
-                  </div>
-                </div>
-              </div>
-              <div class="content-box" v-show="activeTab === 'viewSetting'">
-                <div class="content-list">
-                  <div class="component-zone">
-                    <details>
-                      <summary>요소 선택</summary>
-                      <ul class="component-content">
-                        <li
-                          id="HorizentalTwoData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon2.png"
-                          />
-                        </li>
-                        <li
-                          id="ThreeData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon1.png"
-                          />
-                        </li>
-                        <li
-                          id="HorizentalThreeData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon5.png"
-                          />
-                        </li>
-                        <li
-                          id="TableData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon3.png"
-                          />
-                        </li>
-                        <li class="flex align-center cursor">
-                          <img
-                            src="../../../resources/img/componentIcon/icon4.png"
-                          />
-                        </li>
-                        <!-- 2024.02.16 PJH -->
-                        <li
-                          id="equipmentData"
-                          class="flex align-center cursor"
-                          @click="reOption"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/equipment.png"
-                          />
-                        </li>
-                      </ul>
-                    </details>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- 미리보기  -->
-          <div
-            :class="{
-              'preview-zone': true,
-              flex65: accordion === false,
-              flex95: accordion === true,
-            }"
-          >
-            <div
-              id="splitter-container"
-              ref="splitterContainer"
-              @click="handleSplitterComponentClick"
-              :class="{ active: clickElement === 'splitter-container' }"
-            >
-              <SplitterLayout
-                v-if="showSplit"
-                id="SplitterLayout"
-                :splitInfo="splitInfo"
-                :clickElement="clickElement"
-                :optionChangeClick="optionChangeClick"
-                :componentOptn="componentOptn"
-                :inputVal="inputVal"
-                @parentInfo="parentInfo"
-              >
-              </SplitterLayout>
-            </div>
-            <div class="flex justify-end" id="buttonZone">
-              <button
-                class="blue-border-btn small-btn"
-                id="registerButton"
-                @click="registerLayout"
-              >
-                등록
-              </button>
-              <button
-                class="orange-border-btn small-btn"
-                id="editButton"
-                style="display: none"
-              >
-                수정
-              </button>
-              <button
-                class="darkg-border-btn small-btn"
-                id="resetButton"
-                @click="resetLayout"
-              >
-                초기화
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import SplitterLayout from "../../../views/component/SplitterLayout.vue";
-import JobContainer from "../../component/connection/jobContainer.vue";
-import axios from "axios";
-import store from "../AppStore";
-import SvgIcon from "@jamescoyle/vue-icon";
-import {
-  mdiChevronLeft,
-  mdiChevronRightBox,
-  mdiClose,
-  mdiLandPlots,
-  mdiRhombusSplit,
-} from "@mdi/js";
-import BackgroundOption from "../../component/style/BackgroundOption.vue";
-import CellOption from "../../component/style/CellOption.vue";
-
-export default {
-  openPopup: {
-    type: Boolean,
-    default: false,
-  },
-  data() {
-    return {
-      closePath: mdiClose,
-      // store: store,
-      tabMenu: [
-        { tabName: "레이아웃", tabNic: "columnList", iconPath: mdiLandPlots },
-        {
-          tabName: "컴포넌트",
-          tabNic: "viewSetting",
-          iconPath: mdiRhombusSplit,
-        },
-      ],
-      activeTab: "columnList",
-      showSplit: false, // splitterLayout 태그 렌더링 여부
-      clickElement: null, // 클릭한 요소의 id 값 들어가는 변수
-      layoutVal: null, // 레이아웃 클릭에 따른 layoutNm 값을 위한 변수
-      option2: false,
-      option1: true,
-      // 최상위 레이아웃 정보 담는 객체
-      splitInfo: {
-        splitterId: null, // splitter 고유 id
-        layoutNm: null, // splitter 고유 id
-        type: null, // s, p
-        parentId: null, // 부모 splitterId, 없는 경우 null
-        children: [], // 자식 splitter, panel 정보
-        index: null, // 위치 index
-        minWidth: 0,
-        minHeight: 0,
-        layoutType: null, // horizontal, vertical
-        sizes: [], // splitter 사이즈
-        componentNm: null, // 컴포넌트 이름
-        componentOption: {}, // 컴포넌트 옵션 정보
-      },
-      // 레이아웃 기본 정보 담는 객체
-      splitChildInfo: {
-        layoutNm: null, // splitter 고유 id
-        type: null, // s, p
-        parentId: null, // 부모 splitterId, 없는 경우 null
-        children: [], // 자식 splitter, panel 정보
-        index: null, // 위치 index
-        minWidth: 0,
-        minHeight: 0,
-        layoutType: null, // horizontal, vertical
-        sizes: [], // splitter 사이즈
-        componentNm: null, // 컴포넌트 이름
-        componentOption: {}, // 컴포넌트 옵션 정보
-      },
-      splitDepth: 0, // splitter 고유 id 생성을 위한 깊이
-
-      // 요소 옵션 정보
-      componentOptn: {
-        key: null,
-        parent: null,
-        textData: "new title",
-        columnData: 0,
-        unitData: "%",
-        textSize: 16,
-        textAlign: null,
-        textStyle: null,
-        backColor: null,
-      },
-      clickComponentParentInfo: null,
-      jobGroup: {},
-      getDataTable: {},
-      columnList: [],
-
-      // 게시판 정보
-      customTitle: null, // 제목
-      customComment: null, // 설명
-
-      // 클릭한 요소의 data-type 값
-      selectedOptionDataset: null,
-      selectedLayout: null,
-      isbackColor: false,
-      istextCk: true,
-
-      // 2024.02.16 PJH
-      // Unity project 호출 정보
-      projectInfo: {
-        command: "getProject",
-        projectId: "project1",
-        objectList: [
-          {
-            command: "getObj",
-            projectId: "project1",
-            objectId: "object1",
-            presetId: "preset5",
-            position: { x: 0, y: 0.5, z: 0 },
-            rotation: { x: 0, y: 0, z: 0 },
-            pointList: [],
-          },
-        ],
-      },
-      projects: [],
-      objects: [],
-      points: [],
-      sensors: [],
-      message: {},
-      changeInfo: null,
-      selectPointId: null,
-      nowSensorId: null,
-      pageId: null,
-
-      selectPointId: null,
-      nowSensorId: null,
-      arrowPath: mdiChevronLeft,
-      accordion: false,
-      horizontalImg: require("../../../resources/img/icon/hor_b.png"),
-      verticalImg: require("../../../resources/img/icon/ver_b.png"),
-    };
-  },
-  methods: {
-    showTab: function (tabName) {
-      this.activeTab = tabName;
-    },
-    accordionToggle: function () {
-      this.accordion = !this.accordion;
-      if (!this.accordion) {
-        this.path = mdiChevronLeftBox;
-      } else {
-        this.path = mdiChevronRightBox;
-      }
-    },
-    // 2024.02.16 PJH
-    fetchData: function () {
-      let projectData = [
-        { projectId: "project1", projectName: "모델1" },
-        { projectId: "project2", projectName: "모델2" },
-      ];
-      this.projects = projectData;
-
-      let objectData = [
-        {
-          projectId: "project1",
-          objectId: "object1",
-          objectName: "오브젝트1",
-          presetId: "preset5",
-          position: { x: 0, y: 0.5, z: 0 },
-          rotation: { x: 0, y: 0, z: 0 },
-        },
-        {
-          projectId: "project2",
-          objectId: "object2",
-          objectName: "오브젝트2",
-          presetId: "preset1",
-          position: { x: 0, y: 0.5, z: 0 },
-          rotation: { x: 0, y: 0, z: 0 },
-        },
-      ];
-      this.objects = objectData;
-
-      let sensorData = [
-        { sensorId: "component5", sensorName: "PM2 평량", sensorUse: false },
-        { sensorId: "component6", sensorName: "PM2 수분", sensorUse: false },
-        {
-          sensorId: "component7",
-          sensorName: "PM2 사용스팀",
-          sensorUse: false,
-        },
-        { sensorId: "component8", sensorName: "PM2 보류제", sensorUse: false },
-        {
-          sensorId: "component9",
-          sensorName: "PM2 벤토나이트",
-          sensorUse: false,
-        },
-        { sensorId: "component10", sensorName: "PM2 지력제", sensorUse: false },
-        { sensorId: "component13", sensorName: "PM3 평량", sensorUse: false },
-        { sensorId: "component14", sensorName: "PM3 수분", sensorUse: false },
-        {
-          sensorId: "component15",
-          sensorName: "PM3 사용스팀",
-          sensorUse: false,
-        },
-        { sensorId: "component16", sensorName: "PM3 보류제", sensorUse: false },
-        {
-          sensorId: "component17",
-          sensorName: "PM3 벤토나이트",
-          sensorUse: false,
-        },
-        { sensorId: "component18", sensorName: "PM3 지력제", sensorUse: false },
-      ];
-      this.sensors = sensorData;
-    },
-    reOption(e) {
-      this.selectedLayout = this.clickElement;
-
-      const clickBtn = e.currentTarget.id;
-      if (!this.showSplit) {
-        alert("요소를 삽입할 레이아웃을 선택해주세요");
-        return;
-      }
-      this.addComponentRecursiveFunc(this.splitInfo, clickBtn);
-
-      this.option1 = !this.option1;
-      this.option2 = true;
-    },
-    // 클릭한 요소의 부모 정보를 받아오는 이벤트
-    parentInfo: function (parentInfoq) {
-      this.clickComponentParentInfo = parentInfoq;
-    },
-
-    // 레이아웃 아이콘 클릭에 따른 이벤트
-    addSplitterLayout: function (e, buttonType) {
-      const vm = this;
-
-      // vertical-btn, Horizon-btn 클릭에 따른 layoutVal 값 변경
-      const clickBtn = e.currentTarget.id;
-
-      // 기존 active 제거
-      if (this.activeElement) {
-        this.activeElement.classList.remove("active-layout");
-      }
-
-      // 클릭된 요소에 active-layout 클래스 추가
-      const targetClass = e.currentTarget.classList;
-      targetClass.add("active-layout");
-      this.activeElement = e.currentTarget;
-
-      if (clickBtn == "horizontal-btn") {
-        vm.layoutVal = "horizontal";
-      } else if (clickBtn == "vertical-btn") {
-        vm.layoutVal = "vertical";
-      }
-
-      // 버튼 클릭에 따른 버튼 이미지 변경
-      if (buttonType === "horizontal") {
-        this.horizontalImg = "../../../resources/img/icon/hor_a.png";
-      } else if (buttonType === "vertical") {
-        this.verticalImg = "../../../resources/img/icon/ver_a.png";
-      }
-
-      // 첫 요소가 생기지 않은 상태
-      if (!vm.showSplit) {
-        const createParentId = "split" + vm.splitDepth;
-        //부모의 정보 담기
-        vm.splitInfo["layoutNm"] = createParentId;
-        vm.splitInfo["type"] = "s";
-        vm.splitInfo["parentId"] = null;
-        vm.splitInfo["index"] = 1;
-        vm.splitInfo["minWidth"] = 0;
-        vm.splitInfo["minHeight"] = 0;
-        vm.splitInfo["layoutType"] = vm.layoutVal;
-        vm.splitInfo["sizes"] = [50, 50];
-        vm.splitInfo["componentNm"] = null;
-        vm.splitInfo["componentOption"] = [];
-        for (let i = 0; i < 2; i++) {
-          const createChileId = createParentId + "-panel-" + (i + 1);
-          vm.splitChildInfo["layoutNm"] = createChileId;
-          vm.splitChildInfo["type"] = "s";
-          vm.splitChildInfo["parentId"] = createParentId;
-          vm.splitChildInfo["index"] = i + 1;
-          vm.splitChildInfo["minWidth"] = 0;
-          vm.splitChildInfo["minHeight"] = 0;
-          vm.splitChildInfo["layoutType"] = null;
-          vm.splitChildInfo["sizes"] = [];
-          vm.splitChildInfo["componentNm"] = null;
-          vm.splitChildInfo["componentOption"] = [];
-          vm.splitInfo["children"].push(
-            JSON.parse(JSON.stringify(vm.splitChildInfo))
-          );
-          vm.resetChildrenInfo();
-        }
-        vm.showSplit = true;
-        vm.splitDepth++;
-      } else {
-        // 최상위 레이아웃 변경 적용
-        // SplitterLayout 하위가 있는지 확인
-        if (
-          vm.clickElement === null ||
-          vm.clickElement == "splitter-container"
-        ) {
-          vm.splitInfo["layoutType"] = vm.layoutVal;
-          return;
-        }
-        //  재귀함수 호출
-        this.recursiveFunc(vm.splitInfo);
-      }
-    },
-    // 재귀함수 정의
-    recursiveFunc: function (info) {
-      const vm = this;
-      //클릭된 요소가 '나'고 자식이 존재하면 layoutNm 변경
-      if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length !== 0
-      ) {
-        const childLayout = info["children"][0];
-        childLayout["layoutType"] = vm.layoutVal;
-        return;
-      }
-      //클릭된 요소가 '나'고 자식이 없으면 자식을 추가
-      if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length === 0
-      ) {
-        if (info["componentNm"] !== null) {
-          if (
-            confirm(
-              "삽입된 요소가 있습니다. 레이아웃을 변경하시겠습니까? /n 변경하면 삽인된 요소는 삭제됩니다."
-            ) === false
-          ) {
-            return;
-          }
-        }
-        const myId = "split" + vm.splitDepth;
-
-        // 값을 추가해줘야함
-        vm.splitChildInfo["layoutNm"] = myId;
-        vm.splitChildInfo["type"] = "s";
-        vm.splitChildInfo["parentId"] = vm.clickElement;
-        vm.splitChildInfo["index"] = 1;
-        vm.splitChildInfo["minWidth"] = 0;
-        vm.splitChildInfo["minHeight"] = 0;
-        vm.splitChildInfo["layoutType"] = vm.layoutVal;
-        vm.splitChildInfo["sizes"] = [50, 50];
-        vm.splitChildInfo["componentNm"] = null;
-        vm.splitChildInfo["componentOption"] = [];
-        info["children"].push(JSON.parse(JSON.stringify(vm.splitChildInfo)));
-
-        const myInfo = info["children"][0];
-        for (let n = 0; n < 2; n++) {
-          const childId = myId + "-panel-" + (n + 1);
-          vm.splitChildInfo["layoutNm"] = childId;
-          vm.splitChildInfo["type"] = "p";
-          vm.splitChildInfo["parentId"] = myId;
-          vm.splitChildInfo["index"] = n + 1;
-          vm.splitChildInfo["children"] = [];
-          vm.splitChildInfo["minWidth"] = 0;
-          vm.splitChildInfo["minHeight"] = 0;
-          vm.splitChildInfo["layoutType"] = null;
-          vm.splitChildInfo["sizes"] = [];
-          vm.splitChildInfo["componentNm"] = null;
-          vm.splitChildInfo["componentOption"] = [];
-          myInfo["children"].push(
-            JSON.parse(JSON.stringify(vm.splitChildInfo))
-          );
-          vm.resetChildrenInfo();
-        }
-        vm.splitDepth++;
-        return;
-      }
-      for (let i = 0; i < info["children"].length; i++) {
-        this.recursiveFunc(info["children"][i]);
-      }
-    },
-    // 요소 삽입을 위한 재귀 함수
-    addComponentRecursiveFunc: function (info, clickBtn) {
-      //클릭된 요소 componentName 변경
-      if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length === 0
-      ) {
-        info["componentNm"] = clickBtn;
-      } else if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length !== 0
-      ) {
-        return;
-      }
-
-      for (let i = 0; i < info["children"].length; i++) {
-        this.addComponentRecursiveFunc(info["children"][i], clickBtn);
-      }
-    },
-    // 요소 선택 이벤트 구현
-    addComponent: function (e) {
-      // 요소를 넣을 레이아웃 id값 저장 TODO: 레이아웃 영역만 저장되는가?
-      this.selectedLayout = this.clickElement;
-
-      const clickBtn = e.currentTarget.id;
-      if (!this.showSplit) {
-        alert("요소를 삽입할 레이아웃을 선택해주세요");
-        return;
-      }
-
-      //클릭된 element 에 요소 컴포넌트 추가
-      this.addComponentRecursiveFunc(this.splitInfo, clickBtn);
-
-      this.option2 = !this.option2;
-      this.option1 = true;
-    },
-    // input 값 저장
-    saveInput: function (key, value) {
-      this.parentInfo["componentOption"][this.clickElement] = {};
-      this.componentOptn[key] = value;
-      let isExist = this.parentInfo["componentOption"].hasOwnProperty(
-        this.clickElement
-      );
-
-      if (isExist) {
-        let currentOption =
-          this.parentInfo["componentOption"][this.clickElement];
-        currentOption.textData = this.componentOptn.textData;
-        currentOption.columnData = this.componentOptn.columnData;
-        currentOption.unitData = this.componentOptn.unitData;
-        currentOption.unitCk = this.componentOptn.unitCk;
-        currentOption.textSize = this.componentOptn.textSize;
-        currentOption.textAlign = this.componentOptn.textAlign;
-        currentOption.textStyle = this.componentOptn.textStyle;
-        currentOption.backColor = this.componentOptn.backColor;
-      } else {
-        this.parentInfo["componentOption"][this.clickElement] =
-          this.componentOptn;
-      }
-
-      this.parentInfo["componentOption"][this.clickElement] =
-        this.componentOptn;
-    },
-
-    // 미리보기 내부 splitter / component 클릭 이벤트
-    handleSplitterComponentClick: function (e) {
-      const eventTarget = e.target.id;
-      this.clickElement = eventTarget;
-    },
-    registerLayout: function () {
-      const vm = this;
-
-      if (this.customTitle === null || this.customTitle === "") {
-        alert("제목을 입력해주세요");
-        return;
-      }
-      if (!this.showSplit) {
-        alert("레이아웃 추가해 주세요");
-        return;
-      }
-
-      axios({
-        url: "/custom/insert",
-        method: "post",
-        headers: {
-          "Content-Type": "application/json; charset=UTF-8",
-        },
-        data: {
-          ttl: vm.customTitle,
-          cn: vm.customComment,
-          wrt_id: store.state.loginUser.user_id,
-          splitInfo: vm.splitInfo,
-        },
-      })
-        .then(function (response) {
-          alert("등록되었습니다.");
-        })
-        .catch(function (error) {
-          this.$showAlert(
-            "에러 발생",
-            "에러가 발생했습니다. 관리자에게 문의해 주세요."
-          );
-        });
-    },
-    optionChangeClick: function (e) {
-      // 요소 내 input 클릭 시 옵션 선택 탭으로 이동
-      this.showTab("option");
-      this.option2 = !this.option2;
-      this.option1 = true;
-
-      //배경색상 커스텀
-      if (e.target.dataset.isback) {
-        this.isbackColor = true;
-      } else {
-        this.isbackColor = false;
-      }
-      //텍스트 여부 체크
-      if (e.target.dataset.type === "text") {
-        this.istextCk = true;
-      } else {
-        this.istextCk = false;
-      }
-      const targetId = e.target.id;
-
-      // 클릭한 요소의 데이터를 input에 적용
-      // key를 통해 있으면 데이터 가져와 적용 / 없으면 새로운 데이터 생성
-      let isExist = this.parentInfo["componentOption"].hasOwnProperty(targetId);
-      // 요소 옵션 초기값으로 설정
-      if (!isExist) {
-        this.componentOptn = {
-          columnData: "",
-          textData: "new Title",
-          textSize: null,
-          textAlign: "",
-          textStyle: "",
-          unitData: "%",
-          unitCk: true,
-        };
-      } else {
-        // 있으면 해당 위치 찾아서 데이터 대입
-        const currentOption = this.parentInfo["componentOption"][targetId];
-        this.componentOptn = {
-          columnData: currentOption.columnData,
-          textData: currentOption.textData,
-          textSize: currentOption.textSize,
-          textAlign: currentOption.textAlign,
-          textStyle: currentOption.textStyle,
-          unitData: currentOption.unitData,
-          unitCk: currentOption.unitCk,
-        };
-      }
-    },
-    // 초기화 버튼 클릭 시 최상위 splitInfo 초기화 이벤트
-    resetLayout: function () {
-      this.showSplit = false;
-      this.splitInfo = {
-        layoutNm: null,
-        parentId: null,
-        type: null, // s, p
-        children: [],
-        index: null,
-        componentNm: null,
-        componentOption: [],
-      };
-      this.splitDepth = 0;
-      this.clickElement = "";
-    },
-    resetChildrenInfo: function () {
-      let vm = this;
-      vm.splitChildInfo.layoutNm = null;
-      vm.splitChildInfo.type = null;
-      vm.splitChildInfo.parentId = null;
-      vm.splitChildInfo.children = [];
-      vm.splitChildInfo.index = null;
-      vm.splitChildInfo.minWidth = 0;
-      vm.splitChildInfo.minHeight = 0;
-      vm.splitChildInfo.layoutType = null;
-      vm.splitChildInfo.sizes = [];
-      vm.splitChildInfo.componentNm = null;
-      vm.splitChildInfo.componentOption = {};
-    },
-    // jobContainer에서 받은 데이터 테이블 정보
-    getResult: function (table) {
-      this.columnList = [];
-      for (let i = 0; i < table.columnDatas.length; i++) {
-        // rowData와 columnNm을 포함한 객체 생성
-        let obj = {
-          rowData: table.rowData[0][i],
-          columnNm: table.columnDatas[i].columnNm,
-        };
-        this.columnList.push(obj);
-      }
-    },
-    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(
-            "에러 발생",
-            "에러가 발생했습니다. 관리자에게 문의해 주세요."
-          );
-        });
-    },
-    optionModal: function () {
-      this.isModal = !this.isModal;
-    },
-  },
-  components: {
-    SplitterLayout,
-    JobContainer: JobContainer,
-    SvgIcon: SvgIcon,
-    BackgroundOption: BackgroundOption,
-    CellOption: CellOption,
-  },
-  mounted() {
-    this.activeTab;
-    this.jobGroup = this.$getDefaultJobGroup().jobGroup;
-    this.pageId = this.$route.query.pageId;
-  },
-};
-</script>
-
-<style scoped>
-#splitter-container {
-  width: 100%;
-  height: calc(100% - 50px);
-  border: 1px solid #aaa;
-  margin-bottom: 20px;
-}
-#SplitterLayout {
-  width: 100%;
-  height: 100%;
-}
-#splitter-container.active {
-  border: 3px dotted red;
-}
-#customComment {
-  height: 99px;
-}
-.table-padding {
-  padding: 15px 0;
-}
-.paletteLi {
-  display: inline-block;
-  margin-right: 10px;
-}
-</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/pages/custom/CustomInsert2.vue (deleted)
--- client/views/pages/custom/CustomInsert2.vue
@@ -1,893 +0,0 @@
-<template>
-  <div class="container">
-    <div class="page-titleZone flex justify-between align-start">
-      <p class="main-title">데이터 현황관리 차트 등록</p>
-      <!-- <button @click="test">test</button> -->
-      <PageNavigation />
-    </div>
-    <div class="content-wrap content">
-      <div class="row">
-        <JobContainer :jobGroup="jobGroup" @getDataTable="getResult" />
-      </div>
-      <div class="row custom-tab">
-        <div class="content-box flex justify-between align-start">
-          <div
-            :class="{
-              'tab-zone': true,
-              flex40: accordion === false,
-              flex5: accordion === true,
-            }"
-          >
-            <div class="tab-nav flex justify-between align-center">
-              <ul
-                class="flex justify-start"
-                :style="accordion === true ? { display: 'none' } : {}"
-              >
-                <li
-                  v-for="(tab, idx) in tabMenu"
-                  :key="idx"
-                  @click="showTab(tab.tabNic)"
-                >
-                  <a :class="{ activeTab: activeTab === tab.tabNic }">{{
-                    tab.tabName
-                  }}</a>
-                </li>
-              </ul>
-              <button @click="accordionToggle">
-                <svg-icon type="mdi" :path="path" :color="'#333'"></svg-icon>
-              </button>
-            </div>
-            <div
-              class="tab-content"
-              :style="accordion === true ? { display: 'none' } : {}"
-            >
-              <div class="content-box flex" v-show="activeTab === 'columnList'">
-                <div
-                  class="content-list layout flex-column justify-between flex40"
-                >
-                  <div class="layout-tree">
-                    <span class="section-title mb10">레이아웃 리스트</span>
-                    <ul class="" style="height: 30%"></ul>
-                  </div>
-                  <div class="layout-zone">
-                    <span class="section-title mb10">레이아웃</span>
-                    <ul class="layout-content flex justify-center align-center">
-                      <li
-                        id="horizontal-btn"
-                        class="cursor"
-                        @click="addSplitterLayout"
-                        title="수직레이아웃"
-                      >
-                        <!-- 레이아웃 id 부여 -->
-                        <p
-                          class="vertical-icon flex justify-around align-center"
-                        >
-                          <span></span>
-                          <span></span>
-                        </p>
-                      </li>
-                      <li
-                        id="vertical-btn"
-                        class="cursor"
-                        @click="addSplitterLayout"
-                        title="수평레이아웃"
-                      >
-                        <!-- 레이아웃 id 부여 -->
-                        <p
-                          class="flex-column justify-around align-center horizental-icon"
-                        >
-                          <span></span>
-                          <span></span>
-                        </p>
-                      </li>
-                    </ul>
-                  </div>
-                  <div class="btn-wrap">
-                    <button @click="optionModal" class="blue-btn large-btn">
-                      레이아웃 옵션
-                    </button>
-                  </div>
-                </div>
-                <div class="layout-option flex-column justify-between flex60">
-                  <div class="modal-title">
-                    <div class="flex justify-between align-center">
-                      <span class="section-title">레이아웃 옵션</span>
-                      <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">
-                    <CellOption />
-                    <BackgroundOption />
-                  </div>
-                  <div class="modal-end flex justify-end">
-                    <button class="gray-border-btn small-btn">취소</button>
-                    <button class="blue-border-btn small-btn">적용</button>
-                  </div>
-                </div>
-              </div>
-              <div class="content-box" v-show="activeTab === 'viewSetting'">
-                <div class="content-list">
-                  <div class="component-zone">
-                    <details>
-                      <summary>요소 선택</summary>
-                      <ul class="component-content">
-                        <li
-                          id="HorizentalTwoData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon2.png"
-                          />
-                        </li>
-                        <li
-                          id="ThreeData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon1.png"
-                          />
-                        </li>
-                        <li
-                          id="HorizentalThreeData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon5.png"
-                          />
-                        </li>
-                        <li
-                          id="TableData"
-                          class="flex align-center cursor"
-                          @click="addComponent"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/icon3.png"
-                          />
-                        </li>
-                        <li class="flex align-center cursor">
-                          <img
-                            src="../../../resources/img/componentIcon/icon4.png"
-                          />
-                        </li>
-                        <!-- 2024.02.16 PJH -->
-                        <li
-                          id="equipmentData"
-                          class="flex align-center cursor"
-                          @click="reOption"
-                        >
-                          <img
-                            src="../../../resources/img/componentIcon/equipment.png"
-                          />
-                        </li>
-                      </ul>
-                    </details>
-                  </div>
-                  <div class="chart-zone">
-                    <details>
-                      <summary>chart 선택</summary>
-                      <ul class="component-content flex justify-between">
-                        <li class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/bar_b.png"
-                          />
-                        </li>
-                        <li id="HorizentalTwoData" class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/rowbar_b.png"
-                          />
-                        </li>
-                        <li id="ThreeData" class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/line_b.png"
-                          />
-                        </li>
-                        <li id="HorizentalThreeData" class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/pie_b.png"
-                          />
-                        </li>
-                        <li id="TableData" class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/dounet_b.png"
-                          />
-                        </li>
-                        <li class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/colbarline_b.png"
-                          />
-                        </li>
-                        <li class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/barline_b.png"
-                          />
-                        </li>
-                        <li class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/brokenline_b.png"
-                          />
-                        </li>
-                        <li class="flex30">
-                          <img
-                            src="../../../resources/img/chartIcon/line2_b.png"
-                          />
-                        </li>
-                      </ul>
-                    </details>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <!-- 미리보기  -->
-          <div
-            :class="{
-              'preview-zone': true,
-              flex60: accordion === false,
-              flex95: accordion === true,
-            }"
-          >
-            <div
-              id="splitter-container"
-              ref="splitterContainer"
-              @click="handleSplitterComponentClick"
-              :class="{ active: clickElement === 'splitter-container' }"
-            >
-              <SplitterLayout
-                v-if="showSplit"
-                id="SplitterLayout"
-                :splitInfo="splitInfo"
-                :clickElement="clickElement"
-                :optionChangeClick="optionChangeClick"
-                :componentOptn="componentOptn"
-                :inputVal="inputVal"
-                @parentInfo="parentInfo"
-              >
-              </SplitterLayout>
-            </div>
-            <div class="flex justify-end" id="buttonZone">
-              <button
-                class="blue-border-btn small-btn"
-                id="registerButton"
-                @click="registerLayout"
-              >
-                등록
-              </button>
-              <button
-                class="orange-border-btn small-btn"
-                id="editButton"
-                style="display: none"
-              >
-                수정
-              </button>
-              <button
-                class="darkg-border-btn small-btn"
-                id="resetButton"
-                @click="resetLayout"
-              >
-                초기화
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import SplitterLayout from "../../component/SplitterLayout.vue";
-import JobContainer from "../../component/connection/jobContainer.vue";
-import axios from "axios";
-import store from "../AppStore";
-import SvgIcon from "@jamescoyle/vue-icon";
-import { mdiChevronLeftBox, mdiChevronRightBox, mdiClose } from "@mdi/js";
-import BackgroundOption from "../../component/style/BackgroundOption.vue";
-import CellOption from "../../component/style/CellOption.vue";
-
-export default {
-  openPopup: {
-    type: Boolean,
-    default: false,
-  },
-  data() {
-    return {
-      closePath: mdiClose,
-      // store: store,
-      tabMenu: [
-        { tabName: "레이아웃", tabNic: "columnList" },
-        { tabName: "컴포넌트", tabNic: "viewSetting" },
-      ],
-      activeTab: "columnList",
-      showSplit: false, // splitterLayout 태그 렌더링 여부
-      clickElement: null, // 클릭한 요소의 id 값 들어가는 변수
-      layoutVal: null, // 레이아웃 클릭에 따른 layoutNm 값을 위한 변수
-      option2: false,
-      option1: true,
-      // 최상위 레이아웃 정보 담는 객체
-      splitInfo: {
-        splitterId: null, // splitter 고유 id
-        layoutNm: null, // splitter 고유 id
-        type: null, // s, p
-        parentId: null, // 부모 splitterId, 없는 경우 null
-        children: [], // 자식 splitter, panel 정보
-        index: null, // 위치 index
-        minWidth: 0,
-        minHeight: 0,
-        layoutType: null, // horizontal, vertical
-        sizes: [], // splitter 사이즈
-        componentNm: null, // 컴포넌트 이름
-        componentOption: {}, // 컴포넌트 옵션 정보
-      },
-      // 레이아웃 기본 정보 담는 객체
-      splitChildInfo: {
-        layoutNm: null, // splitter 고유 id
-        type: null, // s, p
-        parentId: null, // 부모 splitterId, 없는 경우 null
-        children: [], // 자식 splitter, panel 정보
-        index: null, // 위치 index
-        minWidth: 0,
-        minHeight: 0,
-        layoutType: null, // horizontal, vertical
-        sizes: [], // splitter 사이즈
-        componentNm: null, // 컴포넌트 이름
-        componentOption: {}, // 컴포넌트 옵션 정보
-      },
-      splitDepth: 0, // splitter 고유 id 생성을 위한 깊이
-
-      // 요소 옵션 정보
-      componentOptn: {
-        key: null,
-        parent: null,
-        textData: "new title",
-        columnData: 0,
-        unitData: "%",
-        textSize: 16,
-        textAlign: null,
-        textStyle: null,
-        backColor: null,
-      },
-      clickComponentParentInfo: null,
-      jobGroup: {},
-      getDataTable: {},
-      columnList: [],
-
-      // 게시판 정보
-      customTitle: null, // 제목
-      customComment: null, // 설명
-
-      // 클릭한 요소의 data-type 값
-      selectedOptionDataset: null,
-      selectedLayout: null,
-      isbackColor: false,
-      istextCk: true,
-
-      // 2024.02.16 PJH
-      // Unity project 호출 정보
-      projectInfo: {
-        command: "getProject",
-        projectId: "project1",
-        objectList: [
-          {
-            command: "getObj",
-            projectId: "project1",
-            objectId: "object1",
-            presetId: "preset5",
-            position: { x: 0, y: 0.5, z: 0 },
-            rotation: { x: 0, y: 0, z: 0 },
-            pointList: [],
-          },
-        ],
-      },
-      projects: [],
-      objects: [],
-      points: [],
-      sensors: [],
-      message: {},
-      changeInfo: null,
-      selectPointId: null,
-      nowSensorId: null,
-      pageId: null,
-
-      selectPointId: null,
-      nowSensorId: null,
-      path: mdiChevronLeftBox,
-      accordion: false,
-    };
-  },
-  methods: {
-    showTab: function (tabName) {
-      this.activeTab = tabName;
-    },
-    accordionToggle: function () {
-      this.accordion = !this.accordion;
-      if (!this.accordion) {
-        this.path = mdiChevronLeftBox;
-      } else {
-        this.path = mdiChevronRightBox;
-      }
-    },
-    // 2024.02.16 PJH
-    fetchData: function () {
-      let projectData = [
-        { projectId: "project1", projectName: "모델1" },
-        { projectId: "project2", projectName: "모델2" },
-      ];
-      this.projects = projectData;
-
-      let objectData = [
-        {
-          projectId: "project1",
-          objectId: "object1",
-          objectName: "오브젝트1",
-          presetId: "preset5",
-          position: { x: 0, y: 0.5, z: 0 },
-          rotation: { x: 0, y: 0, z: 0 },
-        },
-        {
-          projectId: "project2",
-          objectId: "object2",
-          objectName: "오브젝트2",
-          presetId: "preset1",
-          position: { x: 0, y: 0.5, z: 0 },
-          rotation: { x: 0, y: 0, z: 0 },
-        },
-      ];
-      this.objects = objectData;
-
-      let sensorData = [
-        { sensorId: "component5", sensorName: "PM2 평량", sensorUse: false },
-        { sensorId: "component6", sensorName: "PM2 수분", sensorUse: false },
-        {
-          sensorId: "component7",
-          sensorName: "PM2 사용스팀",
-          sensorUse: false,
-        },
-        { sensorId: "component8", sensorName: "PM2 보류제", sensorUse: false },
-        {
-          sensorId: "component9",
-          sensorName: "PM2 벤토나이트",
-          sensorUse: false,
-        },
-        { sensorId: "component10", sensorName: "PM2 지력제", sensorUse: false },
-        { sensorId: "component13", sensorName: "PM3 평량", sensorUse: false },
-        { sensorId: "component14", sensorName: "PM3 수분", sensorUse: false },
-        {
-          sensorId: "component15",
-          sensorName: "PM3 사용스팀",
-          sensorUse: false,
-        },
-        { sensorId: "component16", sensorName: "PM3 보류제", sensorUse: false },
-        {
-          sensorId: "component17",
-          sensorName: "PM3 벤토나이트",
-          sensorUse: false,
-        },
-        { sensorId: "component18", sensorName: "PM3 지력제", sensorUse: false },
-      ];
-      this.sensors = sensorData;
-    },
-    reOption(e) {
-      this.selectedLayout = this.clickElement;
-
-      const clickBtn = e.currentTarget.id;
-      if (!this.showSplit) {
-        alert("요소를 삽입할 레이아웃을 선택해주세요");
-        return;
-      }
-      this.addComponentRecursiveFunc(this.splitInfo, clickBtn);
-
-      this.option1 = !this.option1;
-      this.option2 = true;
-    },
-    // 클릭한 요소의 부모 정보를 받아오는 이벤트
-    parentInfo: function (parentInfoq) {
-      this.clickComponentParentInfo = parentInfoq;
-    },
-
-    // 레이아웃 아이콘 클릭에 따른 이벤트
-    addSplitterLayout: function (e) {
-      const vm = this;
-
-      // vertical-btn, Horizon-btn 클릭에 따른 layoutVal 값 변경
-      const clickBtn = e.currentTarget.id;
-
-      // 기존 active 제거
-      if (this.activeElement) {
-        this.activeElement.classList.remove("active-layout");
-      }
-
-      // 클릭된 요소에 active-layout 클래스 추가
-      const targetClass = e.currentTarget.classList;
-      targetClass.add("active-layout");
-      this.activeElement = e.currentTarget;
-
-      if (clickBtn == "horizontal-btn") {
-        vm.layoutVal = "horizontal";
-      } else if (clickBtn == "vertical-btn") {
-        vm.layoutVal = "vertical";
-      }
-
-      // 첫 요소가 생기지 않은 상태
-      if (!vm.showSplit) {
-        const createParentId = "split" + vm.splitDepth;
-        //부모의 정보 담기
-        vm.splitInfo["layoutNm"] = createParentId;
-        vm.splitInfo["type"] = "s";
-        vm.splitInfo["parentId"] = null;
-        vm.splitInfo["index"] = 1;
-        vm.splitInfo["minWidth"] = 0;
-        vm.splitInfo["minHeight"] = 0;
-        vm.splitInfo["layoutType"] = vm.layoutVal;
-        vm.splitInfo["sizes"] = [50, 50];
-        vm.splitInfo["componentNm"] = null;
-        vm.splitInfo["componentOption"] = [];
-        for (let i = 0; i < 2; i++) {
-          const createChileId = createParentId + "-panel-" + (i + 1);
-          vm.splitChildInfo["layoutNm"] = createChileId;
-          vm.splitChildInfo["type"] = "s";
-          vm.splitChildInfo["parentId"] = createParentId;
-          vm.splitChildInfo["index"] = i + 1;
-          vm.splitChildInfo["minWidth"] = 0;
-          vm.splitChildInfo["minHeight"] = 0;
-          vm.splitChildInfo["layoutType"] = null;
-          vm.splitChildInfo["sizes"] = [];
-          vm.splitChildInfo["componentNm"] = null;
-          vm.splitChildInfo["componentOption"] = [];
-          vm.splitInfo["children"].push(
-            JSON.parse(JSON.stringify(vm.splitChildInfo))
-          );
-          vm.resetChildrenInfo();
-        }
-        vm.showSplit = true;
-        vm.splitDepth++;
-      } else {
-        // 최상위 레이아웃 변경 적용
-        // SplitterLayout 하위가 있는지 확인
-        if (
-          vm.clickElement === null ||
-          vm.clickElement == "splitter-container"
-        ) {
-          vm.splitInfo["layoutType"] = vm.layoutVal;
-          return;
-        }
-        //  재귀함수 호출
-        this.recursiveFunc(vm.splitInfo);
-      }
-    },
-    // 재귀함수 정의
-    recursiveFunc: function (info) {
-      const vm = this;
-      //클릭된 요소가 '나'고 자식이 존재하면 layoutNm 변경
-      if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length !== 0
-      ) {
-        const childLayout = info["children"][0];
-        childLayout["layoutType"] = vm.layoutVal;
-        return;
-      }
-      //클릭된 요소가 '나'고 자식이 없으면 자식을 추가
-      if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length === 0
-      ) {
-        if (info["componentNm"] !== null) {
-          if (
-            confirm(
-              "삽입된 요소가 있습니다. 레이아웃을 변경하시겠습니까? /n 변경하면 삽인된 요소는 삭제됩니다."
-            ) === false
-          ) {
-            return;
-          }
-        }
-        const myId = "split" + vm.splitDepth;
-
-        // 값을 추가해줘야함
-        vm.splitChildInfo["layoutNm"] = myId;
-        vm.splitChildInfo["type"] = "s";
-        vm.splitChildInfo["parentId"] = vm.clickElement;
-        vm.splitChildInfo["index"] = 1;
-        vm.splitChildInfo["minWidth"] = 0;
-        vm.splitChildInfo["minHeight"] = 0;
-        vm.splitChildInfo["layoutType"] = vm.layoutVal;
-        vm.splitChildInfo["sizes"] = [50, 50];
-        vm.splitChildInfo["componentNm"] = null;
-        vm.splitChildInfo["componentOption"] = [];
-        info["children"].push(JSON.parse(JSON.stringify(vm.splitChildInfo)));
-
-        const myInfo = info["children"][0];
-        for (let n = 0; n < 2; n++) {
-          const childId = myId + "-panel-" + (n + 1);
-          vm.splitChildInfo["layoutNm"] = childId;
-          vm.splitChildInfo["type"] = "p";
-          vm.splitChildInfo["parentId"] = myId;
-          vm.splitChildInfo["index"] = n + 1;
-          vm.splitChildInfo["children"] = [];
-          vm.splitChildInfo["minWidth"] = 0;
-          vm.splitChildInfo["minHeight"] = 0;
-          vm.splitChildInfo["layoutType"] = null;
-          vm.splitChildInfo["sizes"] = [];
-          vm.splitChildInfo["componentNm"] = null;
-          vm.splitChildInfo["componentOption"] = [];
-          myInfo["children"].push(
-            JSON.parse(JSON.stringify(vm.splitChildInfo))
-          );
-          vm.resetChildrenInfo();
-        }
-        vm.splitDepth++;
-        return;
-      }
-      for (let i = 0; i < info["children"].length; i++) {
-        this.recursiveFunc(info["children"][i]);
-      }
-    },
-    // 요소 삽입을 위한 재귀 함수
-    addComponentRecursiveFunc: function (info, clickBtn) {
-      //클릭된 요소 componentName 변경
-      if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length === 0
-      ) {
-        info["componentNm"] = clickBtn;
-      } else if (
-        info["layoutNm"] === this.clickElement &&
-        info["children"].length !== 0
-      ) {
-        return;
-      }
-
-      for (let i = 0; i < info["children"].length; i++) {
-        this.addComponentRecursiveFunc(info["children"][i], clickBtn);
-      }
-    },
-    // 요소 선택 이벤트 구현
-    addComponent: function (e) {
-      // 요소를 넣을 레이아웃 id값 저장 TODO: 레이아웃 영역만 저장되는가?
-      this.selectedLayout = this.clickElement;
-
-      const clickBtn = e.currentTarget.id;
-      if (!this.showSplit) {
-        alert("요소를 삽입할 레이아웃을 선택해주세요");
-        return;
-      }
-
-      //클릭된 element 에 요소 컴포넌트 추가
-      this.addComponentRecursiveFunc(this.splitInfo, clickBtn);
-
-      this.option2 = !this.option2;
-      this.option1 = true;
-    },
-    // input 값 저장
-    saveInput: function (key, value) {
-      this.parentInfo["componentOption"][this.clickElement] = {};
-      this.componentOptn[key] = value;
-      let isExist = this.parentInfo["componentOption"].hasOwnProperty(
-        this.clickElement
-      );
-
-      if (isExist) {
-        let currentOption =
-          this.parentInfo["componentOption"][this.clickElement];
-        currentOption.textData = this.componentOptn.textData;
-        currentOption.columnData = this.componentOptn.columnData;
-        currentOption.unitData = this.componentOptn.unitData;
-        currentOption.unitCk = this.componentOptn.unitCk;
-        currentOption.textSize = this.componentOptn.textSize;
-        currentOption.textAlign = this.componentOptn.textAlign;
-        currentOption.textStyle = this.componentOptn.textStyle;
-        currentOption.backColor = this.componentOptn.backColor;
-      } else {
-        this.parentInfo["componentOption"][this.clickElement] =
-          this.componentOptn;
-      }
-
-      this.parentInfo["componentOption"][this.clickElement] =
-        this.componentOptn;
-    },
-
-    // 미리보기 내부 splitter / component 클릭 이벤트
-    handleSplitterComponentClick: function (e) {
-      const eventTarget = e.target.id;
-      this.clickElement = eventTarget;
-    },
-    registerLayout: function () {
-      const vm = this;
-
-      if (this.customTitle === null || this.customTitle === "") {
-        alert("제목을 입력해주세요");
-        return;
-      }
-      if (!this.showSplit) {
-        alert("레이아웃 추가해 주세요");
-        return;
-      }
-
-      axios({
-        url: "/custom/insert",
-        method: "post",
-        headers: {
-          "Content-Type": "application/json; charset=UTF-8",
-        },
-        data: {
-          ttl: vm.customTitle,
-          cn: vm.customComment,
-          wrt_id: store.state.loginUser.user_id,
-          /* jobGroup:this.jobGroup,
-                    getDataTable: this.getDataTable,*/
-          splitInfo: vm.splitInfo,
-        },
-      })
-        .then(function (response) {
-          alert("등록되었습니다.");
-        })
-        .catch(function (error) {
-          this.$showAlert(
-            "에러 발생",
-            "에러가 발생했습니다. 관리자에게 문의해 주세요."
-          );
-        });
-    },
-    optionChangeClick: function (e) {
-      // 요소 내 input 클릭 시 옵션 선택 탭으로 이동
-      this.showTab("option");
-      this.option2 = !this.option2;
-      this.option1 = true;
-
-      //배경색상 커스텀
-      if (e.target.dataset.isback) {
-        this.isbackColor = true;
-      } else {
-        this.isbackColor = false;
-      }
-      //텍스트 여부 체크
-      if (e.target.dataset.type === "text") {
-        this.istextCk = true;
-      } else {
-        this.istextCk = false;
-      }
-      const targetId = e.target.id;
-
-      // 클릭한 요소의 데이터를 input에 적용
-      // key를 통해 있으면 데이터 가져와 적용 / 없으면 새로운 데이터 생성
-      let isExist = this.parentInfo["componentOption"].hasOwnProperty(targetId);
-      // 요소 옵션 초기값으로 설정
-      if (!isExist) {
-        this.componentOptn = {
-          columnData: "",
-          textData: "new Title",
-          textSize: null,
-          textAlign: "",
-          textStyle: "",
-          unitData: "%",
-          unitCk: true,
-        };
-      } else {
-        // 있으면 해당 위치 찾아서 데이터 대입
-        const currentOption = this.parentInfo["componentOption"][targetId];
-        this.componentOptn = {
-          columnData: currentOption.columnData,
-          textData: currentOption.textData,
-          textSize: currentOption.textSize,
-          textAlign: currentOption.textAlign,
-          textStyle: currentOption.textStyle,
-          unitData: currentOption.unitData,
-          unitCk: currentOption.unitCk,
-        };
-      }
-    },
-    // 초기화 버튼 클릭 시 최상위 splitInfo 초기화 이벤트
-    resetLayout: function () {
-      this.showSplit = false;
-      this.splitInfo = {
-        layoutNm: null,
-        parentId: null,
-        type: null, // s, p
-        children: [],
-        index: null,
-        componentNm: null,
-        componentOption: [],
-      };
-      this.splitDepth = 0;
-      this.clickElement = "";
-    },
-    resetChildrenInfo: function () {
-      let vm = this;
-      vm.splitChildInfo.layoutNm = null;
-      vm.splitChildInfo.type = null;
-      vm.splitChildInfo.parentId = null;
-      vm.splitChildInfo.children = [];
-      vm.splitChildInfo.index = null;
-      vm.splitChildInfo.minWidth = 0;
-      vm.splitChildInfo.minHeight = 0;
-      vm.splitChildInfo.layoutType = null;
-      vm.splitChildInfo.sizes = [];
-      vm.splitChildInfo.componentNm = null;
-      vm.splitChildInfo.componentOption = {};
-    },
-    // jobContainer에서 받은 데이터 테이블 정보
-    getResult: function (table) {
-      this.columnList = [];
-      for (let i = 0; i < table.columnDatas.length; i++) {
-        // rowData와 columnNm을 포함한 객체 생성
-        let obj = {
-          rowData: table.rowData[0][i],
-          columnNm: table.columnDatas[i].columnNm,
-        };
-        this.columnList.push(obj);
-      }
-    },
-    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(
-            "에러 발생",
-            "에러가 발생했습니다. 관리자에게 문의해 주세요."
-          );
-        });
-    },
-    optionModal: function () {
-      this.isModal = !this.isModal;
-    },
-  },
-  components: {
-    SplitterLayout,
-    JobContainer: JobContainer,
-    SvgIcon: SvgIcon,
-    BackgroundOption: BackgroundOption,
-    CellOption: CellOption,
-  },
-  mounted() {
-    this.activeTab;
-    this.jobGroup = this.$getDefaultJobGroup().jobGroup;
-    this.pageId = this.$route.query.pageId;
-  },
-};
-</script>
-
-<style scoped>
-#splitter-container {
-  width: 100%;
-  height: calc(100% - 50px);
-  border: 1px solid #aaa;
-  margin-bottom: 20px;
-}
-#SplitterLayout {
-  width: 100%;
-  height: 100%;
-}
-#splitter-container.active {
-  border: 3px dotted red;
-}
-#customComment {
-  height: 99px;
-}
-.table-padding {
-  padding: 15px 0;
-}
-.paletteLi {
-  display: inline-block;
-  margin-right: 10px;
-}
-</style>(파일 끝에 줄바꿈 문자 없음)
 
client/views/pages/custom/CustomInsertDev.vue (deleted)
--- client/views/pages/custom/CustomInsertDev.vue
@@ -1,1012 +0,0 @@
-<template>
-  <div class="container rrmse-wrap" :style="isLoading ? { cursor: 'wait' } : {}">
-    <!-- 로딩 시작 { -->
-    <div v-if="isLoading" class="loading-overlay">
-      <div class="loading-div">
-        <span>LOADING</span>
-        <span class="anima">.</span>
-        <span class="anima">.</span>
-        <span class="anima">.</span>
-      </div>
-    </div>
-    <!-- } 로딩 종료 -->
-    <div class="page-titleZone flex justify-between align-center align-start">
-      <p class="main-title flex80">데이터 활용관리 {{ !isUpdatePage ? '등록' : '수정' }}</p>
-    </div>
-    <div class="content-wrap content flex100">
-      <div class="custom-tab content-box">
-        <div class="flex justify-start align-center no-gutter content-box">
-          <!-- 네비게이션 시작 { -->
-          <div class="column-nav flex5">
-            <ul>
-              <li v-for="(item, idx) in navList" :key="idx" @click="showTab('nav', item.id)" class="cursor">
-                <a :class="{ activeTab: activeTab === item.id }" :title="item.name">
-                  <svg-icon type="mdi" :path="item.iconPath" class="mb5"></svg-icon>
-                  <p>{{ item.name }}</p>
-                </a>
-              </li>
-            </ul>
-          </div>
-          <!-- } 네비게이션 종료 -->
-          <!-- 네비게이션 화면 { -->
-          <div class="content-box flex justify-between align-start flex95 no-gutter">
-            <div :class="{
-              'tab-zone flex align-start': true,
-              flex30: accordion === false,
-              flex0: accordion === true,
-            }">
-              <div class="content-box flex100 pl0" :style="accordion === true ? { display: 'none' } : ''">
-                <div class="content-box flex justify-between tab-box overflow-y" v-show="activeTab === 'pageInfo'">
-                  <div class="content-list layout flex100">
-                    <div class="page-info content-box">
-                      <div class="content-titleZone">
-                        <div class="flex justify-between align-center">
-                          <p class="box-title">페이지 정보</p>
-                        </div>
-                      </div>
-                      <div class="info-area">
-                        <div class="input-group mb10">
-                          <label for="" class="mb10">제목</label>
-                          <input type="text" name="" id="" class="full-input" v-model="customTitle" @input="customTitle = $event.target.value" />
-                        </div>
-                        <div class="input-group mb10">
-                          <label for="" class="mb10">공개여부</label>
-                          <div class="input-container flex">
-                            <label class="radio-label">
-                              <input type="radio" name="public_at" class="custom-radiobox" :value="true" v-model="public_at" />
-                              <span>공개</span>
-                            </label>
-                            <label class="radio-label">
-                              <input type="radio" name="public_at" class="custom-radiobox" :value="false" v-model="public_at" />
-                              <span>비공개</span>
-                            </label>
-                          </div>
-                        </div>
-                        <div class="input-group" style="height: calc(100% - 150px)">
-                          <label for="" class="mb10">설명</label>
-                          <textarea name="" id="" cols="30" rows="35" v-model="customComment" @input="customComment = $event.target.value" class="content-box"></textarea>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-                <div class="content-box tab-box" v-show="activeTab === 'dataset'">
-                  <div class="content-list content-box">
-                    <VueFlowZoneGroup :diagramList="diagramList" :splitInfo="splitInfo" @axisData="handlerAxisData" @clickDiagramDataTable="getDiagramDataInfo" @selectedCal="changeSelectCal" @updateSplitInfo="updateSplitInfo" :layoutChartData="selectLayout" />
-                  </div>
-                </div>
-                <div class="content-box flex justify-between tab-box" v-show="activeTab === 'viewSetting'">
-                  <div class="content-list layout flex50">
-                    <div class="layout-tree content-box">
-                      <div class="content-titleZone">
-                        <div class="flex justify-between align-center">
-                          <p class="box-title">레이아웃 및 컴포넌트</p>
-                          <div>
-                            <button id="horizontal-btn" @click="addSplitterLayout($event, 'vertical')" title="수직레이아웃">
-                              <img :src="verticalImg" />
-                            </button>
-                            <button id="vertical-btn" @click="addSplitterLayout($event, 'horizontal')" title="수평레이아웃">
-                              <img :src="horizontalImg" />
-                            </button>
-                            <button id="split-delete-btn" @click="deleteSplitterLayout" title="삭제">
-                              <svg-icon type="mdi" :path="trashPath" :color="'#aaa'"></svg-icon>
-                            </button>
-                            <button class="attribute-btn" @click="attributeSelected">
-                              <svg-icon type="mdi" :path="dotPath" :color="'#aaa'"></svg-icon>
-                              <div class="attribute-modal" v-show="attribute">
-                                <ul>
-                                  <li class="flex align-center">
-                                    <svg-icon type="mdi" :path="tablePath"></svg-icon>
-                                    <p class="ml5">테이블 추가</p>
-                                  </li>
-                                  <li class="flex align-center">
-                                    <svg-icon type="mdi" :path="chartPath"></svg-icon>
-                                    <p class="ml5">차트 추가</p>
-                                  </li>
-                                </ul>
-                              </div>
-                            </button>
-                          </div>
-                        </div>
-                      </div>
-                      <ul>
-                        <TreeItem :splitInfo="splitInfo" :selectLayout="selectLayout" @changeselectLayout="changeselectLayout" />
-                      </ul>
-                    </div>
-                  </div>
-                  <div class="layout-option flex-column flex50">
-                    <div class="tabnav3">
-                      <ul class="flex justify-start align-center">
-                        <li v-for="(nav, idx) in optionList" @click="showTab('opt', nav.name)" :key="idx" class="cursor">
-                          <p :class="{
-                            activeOption: activeOption === nav.name,
-                          }">{{ nav.name }}</p>
-                        </li>
-                      </ul>
-                    </div>
-                    <div v-show="activeOption === '레이아웃'">
-                      <StyleSheet :styleSheet="selectLayout.styleSheet" />
-                    </div>
-                    <div v-show="activeOption === '컴포넌트'">
-                      <StyleSheet :styleSheet="selectLayout.styleSheet" />
-                    </div>
-                    <div v-show="activeOption === '타이틀'">
-                      <TitleStyleSheet :component="selectLayout.component" v-if="selectLayout.component" />
-                    </div>
-                  </div>
-                </div>
-                <div class="content-box flex justify-between tab-box" v-show="activeTab === 'component'">
-                  <div class="content-box overflow-y">
-                    <ul class="flex justify-center">
-                      <li v-for="(cmpnt, idx) in chartComponent" :key="idx" class="flex5 mt10 cursor" @click="
-                        addComponent(idx, cmpnt.type, cmpnt.id, cmpnt.name)
-                        ">
-                        <div :class="{ 'img-wrap border': true, active: isActiveIndex(idx) }">
-                          <img :src="cmpnt.src" alt="" />
-                          <div class="img-hover">
-                            <span>{{ cmpnt.name }}</span>
-                          </div>
-                        </div>
-                      </li>
-                    </ul>
-                  </div>
-                </div>
-              </div>
-              <button @click="accordionToggle" class="custom-toggle" :style="accordion === true ? { right: '-25px' } : { right: '-14px' }">
-                <svg-icon type="mdi" :path="arrowPath" :color="'#333'"></svg-icon>
-              </button>
-            </div>
-            <!-- 미리보기  -->
-            <div :class="{
-              'preview-zone': true,
-              flex70: accordion === false,
-              flex95: accordion === true,
-            }">
-              <div id="splitter-container" ref="splitterContainer" @click="handleSplitterComponentClick" :class="{ active: clickElement === 'splitter-container' }" class="splitter-panel-custom">
-                <SplitterLayout id="SplitterLayout" :splitInfo="splitInfo_dumy" @changeselectLayout="changeselectLayout" :selectLayout="selectLayout" @parentInfo="parentInfo" :createChartData="createChartData">
-                </SplitterLayout>
-              </div>
-              <div class="flex justify-end" id="buttonZone">
-                <button v-if="!isUpdatePage" class="blue-btn small-btn" id="registerButton" @click="registerLayout">등록</button>
-                <button v-else class="blue-btn small-btn" id="editButton" @click="registerLayout">수정</button>
-                <button class="blue-border-btn small-btn" id="resetButton" @click="initClickEvent">초기화</button>
-                <button class="darkg-btn small-btn" id="resetButton" @click="moveBack">취소</button>
-              </div>
-            </div>
-            <!-- 미리보기  -->
-          </div>
-          <!-- } 네비게이션 화면 종료 -->
-        </div>
-      </div>
-    </div>
-  </div>
-  <!-- 데이터 설정 모달 시작 { -->
-  <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">
-        <JobContainer :jobGroup="jobGroup" @getDataTable="getResult" />
-      </div>
-      <div class="modal-end flex justify-end">
-        <button class="orange-btn small-btn">접속</button>
-        <button class="blue-btn small-btn">확인</button>
-      </div>
-    </div>
-  </div>
-  <!-- } 데이터 설정 모달 종료 -->
-</template>
-<script>
-import SplitterLayout from "../../component/SplitterLayoutDev.vue";
-import JobContainer from "../../component/connection/jobContainer.vue";
-import axios from "axios";
-import store from "../AppStore";
-import SvgIcon from "@jamescoyle/vue-icon";
-import TreeItem from "../../component/LayoutTree.vue";
-import { mdiChevronLeft, mdiChevronRight, mdiLandPlots, mdiRhombusSplit, mdiDatabaseCogOutline, mdiInformationSlabCircleOutline, mdiDotsVertical, mdiTableLarge, mdiChartLine, mdiDatabase, mdiTrashCanOutline, } from "@mdi/js";
-import StyleSheet from "../../component/style/StyleSheetComponent.vue";
-import TitleStyleSheet from "../../component/style/TitleStyleComponent.vue";
-import BackgroundOption from "../../component/style/BackgroundOption.vue";
-import CellOption from "../../component/style/CellOption.vue";
-import FontOption from "../../component/style/FontOption.vue";
-import VueFlowZoneGroup from "../../component/flowComponent/VueFlowZoneGroup.vue";
-import _ from "lodash";
-import chartDataTransform from "../../component/chart/chartDataTransform.js";
-import html2canvas from "html2canvas";
-import { split } from "lodash";
-
-export default {
-  openPopup: {
-    type: Boolean,
-    default: false,
-  },
-  data() {
-    return {
-      navList: [
-        { id: "pageInfo", name: "페이지 정보", iconPath: mdiInformationSlabCircleOutline, },
-        { id: "viewSetting", name: "레이아웃", iconPath: mdiLandPlots },
-        { id: "component", name: "컴포넌트", iconPath: mdiRhombusSplit },
-        { id: "dataset", name: "데이터", iconPath: mdiDatabaseCogOutline, },
-      ],
-      optionList: [
-        { id: "layoutOption", name: "레이아웃", },
-        { id: "titleOption", name: "타이틀", },
-        { id: "componentOption", name: "컴포넌트", },
-      ],
-      activeTab: "pageInfo", // default 페이지 정보
-
-      // 최상위 레이아웃 정보 담는 객체
-      splitInfo: _.cloneDeep(this.$getDefaultJobGroup().customSplitter),
-      splitInfo_dumy: _.cloneDeep(this.$getDefaultJobGroup().customSplitter),
-      jobGroup: {},
-      getDataTable: {},
-      columnList: [],
-
-      // 게시판 정보
-      customTitle: null, // 제목
-      customComment: null, // 설명
-
-      selectLayout: {},
-      arrowPath: mdiChevronLeft,
-      accordion: false,
-      horizontalImg: require("../../../resources/img/icon/hor_b.png"),
-      verticalImg: require("../../../resources/img/icon/ver_b.png"),
-      dotPath: mdiDotsVertical,
-      tablePath: mdiTableLarge,
-      chartPath: mdiChartLine,
-      dataPath: mdiDatabase,
-      attribute: false,
-      dataItm: [],
-      diagramList: [],
-      chartComponent: [
-        {
-          src: require("../../../resources/img/chartIcon/column_chart_v.png"),
-          type: "chart",
-          id: "column_chart_v",
-          name: "세로형 막대 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/column_chart_h.png"),
-          type: "chart",
-          id: "column_chart_h",
-          name: "가로형 막대 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/clustered_chart_v.png"),
-          type: "chart",
-          id: "clustered_chart_v",
-          name: "세로형 클러스터 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/clustered_chart_h.png"),
-          type: "chart",
-          id: "clustered_chart_h",
-          name: "가로형 클러스터 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/stacked_bar_chart.png"),
-          type: "chart",
-          id: "stacked_bar_chart",
-          name: "적층 막대 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/line_chart.png"),
-          type: "chart",
-          id: "line_chart",
-          name: "선 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/mix_chart.png"),
-          type: "chart",
-          id: "mix_chart",
-          name: "혼합 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/pie_chart.png"),
-          type: "chart",
-          id: "pie_chart",
-          name: "파이 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/dounet_chart.png"),
-          type: "chart",
-          id: "dounet_chart",
-          name: "도넛 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/semicircle_chart.png"),
-          type: "chart",
-          id: "semicircle_chart",
-          name: "반원 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/bubble_chart.png"),
-          type: "chart",
-          id: "bubble_chart",
-          name: "버블 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/word_chart.png"),
-          type: "chart",
-          id: "word_chart",
-          name: "단어 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/stacked_column_chart.png"),
-          type: "chart",
-          id: "stacked_column_chart",
-          name: "세로형 적층 막대 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/node_line_chart.png"),
-          type: "chart",
-          id: "node_line_chart",
-          name: "노드 라인 차트",
-        },
-        {
-          src: require("../../../resources/img/chartIcon/liin_chart_back.png"),
-          type: "chart",
-          id: "liin_chart_back",
-          name: "라인 백그라운드 차트",
-        },
-      ],
-      activeIndex: null,
-      dataCount: 1,
-      isModalOpen: false,
-      activeOption: "레이아웃",
-      trashPath: mdiTrashCanOutline,
-
-      // x, y축 (객체 형식으로 데이터 수정 필요)
-      createChartData: _.cloneDeep(this.$getDefaultJobGroup().componentData),
-      clickComponentParentInfo: {},
-      rowData: [], // 선택한 다이어그램의 데이터 테이블을 담는 객체
-      clickDiagramInfo: {}, // 선택한 다이어그램의 정보를 담는 객체
-
-      pageId: null,
-      isLoading: false,
-      isUpdatePage: false,
-
-      public_at: true,
-    };
-  },
-  methods: {
-    // 탭 전환
-    showTab(type, current) {
-      if (type == 'nav') {
-        this.activeTab = current;
-      } else if (type == 'opt') {
-        this.activeOption = current;
-      }
-    },
-    accordionToggle: function () {
-      this.accordion = !this.accordion;
-      if (!this.accordion) {
-        this.arrowPath = mdiChevronLeft;
-      } else {
-        this.arrowPath = mdiChevronRight;
-      }
-    },
-    setModal: function () {
-      this.isModalOpen = true;
-    },
-    closeModal: function () {
-      this.isModalOpen = false;
-    },
-    attributeSelected: function () {
-      this.attribute = !this.attribute;
-    },
-
-    dataAdd: function () {
-      this.dataItm.push("Data" + this.dataCount);
-      this.dataCount++;
-    },
-
-    // 컴포넌트 추가
-    addComponent: async function (index, type, id, name) {
-      this.activeIndex = index;
-      if (id == "bubble_chart") {
-        this.$showAlert("메세지", "해당 차트는 현재 지원하지 않습니다.");
-        return;
-      }
-      if (
-        await this.$showConfirm(
-          "컴포넌트 추가",
-          id + "컴포넌트를 추가하시겠습니까?"
-        )
-      ) {
-        if (this.selectLayout.children.length > 0) {
-          this.$showAlert("메세지", "레이아웃이 존재합니다.");
-          return;
-        }
-        if (this.selectLayout == null) {
-          this.$showAlert("메세지", "선택된 레이아웃이 없습니다.");
-          return;
-        }
-        this.selectLayout.component = {};
-        this.selectLayout.se = "component";
-        let component = _.cloneDeep(this.$getDefaultJobGroup().component);
-        let chartData = _.cloneDeep(this.$getDefaultJobGroup().componentData);
-        let jobGroup = _.cloneDeep(this.$getDefaultJobGroup().jobGroup);
-
-        // 차트 생성 데이터 세팅 (chartName, componentName)
-        chartData.chart_knd = id;
-        chartData.component_nm = "ChartCmmn";
-        chartData.component_type = type;
-
-        component.component_itm = chartData;
-        component.jobInfo = [];
-        component.jobInfo.push(jobGroup);
-
-        this.selectLayout.component = component;
-      } else {
-        this.getSelectLayoutChart();
-      }
-    },
-    getSelectLayoutChart: function () {
-      // 현재 선택된 컴포넌트 차트 정보로 변경
-      if (this.selectLayout.se == "component") {
-        if (!this.selectLayout.component.component_itm.chart_knd) {
-          this.activeIndex = null;
-          return;
-        }
-        this.chartComponent.forEach((item, index) => {
-          if (item.id === this.selectLayout.component.component_itm.chart_knd) {
-            this.activeIndex = index;
-          }
-        });
-      }
-    },
-
-    isActiveIndex: function (index) {
-      return this.activeIndex === index;
-    },
-
-    // 선택된 레이어웃 반환
-    changeselectLayout: function (selectLayout) {
-      this.selectLayout = selectLayout;
-      // 차트 리스트 중 인덱스 찾아서 activeIndex에 할당
-      this.getSelectLayoutChart();
-    },
-
-    // 클릭한 요소의 부모 정보를 받아오는 이벤트
-    parentInfo: function (info) {
-      this.clickComponentParentInfo = info;
-    },
-
-    // 레이아웃 아이콘 클릭에 따른 이벤트
-    addSplitterLayout: async function (e, buttonType) {
-      const vm = this;
-      // vertical-btn, Horizon-btn 클릭에 따른 layoutVal 값 변경
-      const clickBtn = e.currentTarget.id;
-
-      let layoutVal = "horizontal";
-
-      // 레이아웃 확장
-      let extend = false;
-      // 기존 active 제거
-      if (this.activeElement) {
-        this.activeElement.classList.remove("active-layout");
-      }
-      // 클릭된 요소에 active-layout 클래스 추가
-      const targetClass = e.currentTarget.classList;
-      targetClass.add("active-layout");
-
-      this.activeElement = e.currentTarget;
-      if (clickBtn == "horizontal-btn") {
-        layoutVal = "horizontal";
-      } else if (clickBtn == "vertical-btn") {
-        layoutVal = "vertical";
-      }
-
-      // 버튼 클릭에 따른 버튼 이미지 변경
-      if (buttonType === "horizontal") {
-        this.verticalImg = require("../../../resources/img/icon/ver_b.png");
-        this.horizontalImg = require("../../../resources/img/icon/hor_a.png");
-      } else if (buttonType === "vertical") {
-        this.horizontalImg = require("../../../resources/img/icon/hor_b.png");
-        this.verticalImg = require("../../../resources/img/icon/ver_a.png");
-      }
-
-      // 유효성 검사
-      if (this.selectLayout.layout_nm == null) {
-        this.$showAlert("메세지", "선택된 레이아웃이 없습니다.");
-        return;
-      } else if (
-        this.selectLayout.children.length > 0 ||
-        this.selectLayout.se == "component"
-      ) {
-        if (
-          await this.$showConfirm(
-            "메세지",
-            "선택된 레이아웃을 분할하시겠습니까?"
-          )
-        ) {
-          extend = true;
-        } else {
-          return;
-        }
-      }
-
-      // 선택된 객체 세팅
-      const temp_layout = _.cloneDeep(this.selectLayout);
-      this.selectLayout.se = "splitter";
-      this.selectLayout.layout_type = _.cloneDeep(layoutVal);
-      this.selectLayout.layout_size1 = 50;
-      this.selectLayout.layout_size2 = 50;
-
-      let tempChilds = [];
-      // 자식요소 추가
-      if (extend) {
-        tempChilds = _.cloneDeep(this.selectLayout.children);
-        this.selectLayout.children = [];
-        this.selectLayout.layout_nm = Math.random()
-          .toString(36)
-          .substring(2, 15);
-      }
-      for (let i = 0; i < 2; i++) {
-        let tempChild = _.cloneDeep(this.$getDefaultJobGroup().customSplitter);
-        tempChild.depth = this.selectLayout.depth + 1;
-        tempChild.parents_splitter_id = this.selectLayout.layout_nm;
-        tempChild.position_idx = i;
-        tempChild.styleSheet.borderStyle = _.cloneDeep(
-          this.$getDefaultJobGroup().borderStyle
-        );
-        tempChild.styleSheet.background_style = _.cloneDeep(
-          this.$getDefaultJobGroup().background_style
-        );
-        if (extend && i == 0) {
-          tempChild.layout_nm = temp_layout.layout_nm;
-          tempChild.se = temp_layout.se;
-          tempChild.component = _.cloneDeep(this.selectLayout.component);
-          tempChild.layout_type = temp_layout.layout_type;
-          this.selectLayout.component = null;
-          tempChild.children = tempChilds;
-          this.selectLayout.children.push(tempChild);
-        } else {
-          tempChild.layout_nm = Math.random().toString(36).substring(2, 15);
-          tempChild.se = "splitter";
-          tempChild.children = [];
-          this.selectLayout.children.push(tempChild);
-        }
-      }
-    },
-
-    registerLayout: function () {
-      let vm = this;
-      // 유효성 검사
-      if (vm.customTitle == null || vm.customTitle == "") {
-        vm.$showAlert("메세지", "제목은 필수입력입니다.");
-        return;
-      }
-      if (vm.splitInfo.component == null && vm.splitInfo.children.length == 0) {
-        vm.$showAlert("메세지", "레이아웃 또는 차트를 추가해야합니다.");
-        return;
-      }
-      if (vm.pageId) {
-        // 저장 로직 구현
-        let vm = this;
-        // 캡처할 요소
-        let element = document.getElementById("splitter-container");
-
-        // html2canvas 사용하여 element 캡처
-        html2canvas(element).then((canvas) => {
-          // 캔버스를 Blob 데이터로 변환
-          canvas.toBlob(function (blob) {
-            let imgUrl = blob;
-
-            let formData = new FormData();
-            formData.append("img", imgUrl, "capture.png"); // Blob 데이터 사용
-
-            formData.append("page_id", vm.pageId);
-            formData.append("ttl", vm.customTitle);
-            formData.append("cn", vm.customComment);
-            formData.append("mdfcn_id", store.state.loginUser.user_id);
-            formData.append("splitInfo", JSON.stringify(vm.splitInfo)); // JSON 문자열로 변환
-            formData.append(
-              "jobFlow",
-              JSON.stringify({ jobGroup: vm.diagramList })
-            ); // JSON 문자열로 변환
-            formData.append("public_at", vm.public_at);
-
-            axios({
-              url: "/custom/customUpdate",
-              method: "post",
-              headers: {
-                "Content-Type": "multipart/form-data",
-              },
-              data: formData,
-            })
-              .then(function (response) {
-                if (response.data.checkMessage.success) {
-                  vm.$showAlert("메세지", "페이지 수정에 성공하였습니다.");
-                  vm.$router.push({
-                    path: "/customSelectOne.page",
-                    query: { page_id: vm.pageId },
-                  });
-                } else {
-                  vm.$showAlert("메세지", "수정에 실패하였습니다.");
-                }
-              })
-              .catch(function (error) {
-                if (error.response.data.code == "COMPONENT_DATA_NOT_FOUND") {
-                  vm.$showAlert(
-                    "메세지",
-                    "데이터를 추가하지 않은 컴포넌트가 존재합니다."
-                  );
-                }
-              });
-          });
-        });
-      } else {
-        // canvas로 화면 이미지 저장하여 같이 저장
-        let vm = this;
-        // 캡처할 요소
-        let element = document.getElementById("splitter-container");
-
-        // html2canvas 사용하여 element 캡처
-        html2canvas(element).then((canvas) => {
-          // 캔버스를 Blob 데이터로 변환
-          canvas.toBlob(function (blob) {
-            let imgUrl = blob;
-
-            let formData = new FormData();
-            formData.append("img", imgUrl, "capture.png"); // Blob 데이터 사용
-
-            formData.append("ttl", vm.customTitle);
-            formData.append("cn", vm.customComment);
-            formData.append("wrt_id", store.state.loginUser.user_id);
-            formData.append("splitInfo", JSON.stringify(vm.splitInfo)); // JSON 문자열로 변환
-            formData.append(
-              "jobFlow",
-              JSON.stringify({ jobGroup: vm.diagramList })
-            ); // JSON 문자열로 변환
-            formData.append("public_at", vm.public_at);
-
-            axios({
-              url: "/custom/insert",
-              method: "post",
-              headers: {
-                "Content-Type": "multipart/form-data",
-              },
-              data: formData,
-            })
-              .then(function (response) {
-                if (response.data.checkMessage.success) {
-                  let createPageId = response.data.resultData.pageId;
-                  vm.$showAlert("메세지", "페이지 등록에 성공하였습니다.");
-                  vm.$router.push({
-                    path: "/customSelectOne.page",
-                    query: { page_id: createPageId },
-                  });
-                } else {
-                  vm.$showAlert("메세지", "등록에 실패하였습니다.");
-                }
-              })
-              .catch(function (error) {
-                if (error.response.data.code == "COMPONENT_DATA_NOT_FOUND") {
-                  vm.$showAlert(
-                    "메세지",
-                    "데이터를 추가하지 않은 컴포넌트가 존재합니다."
-                  );
-                }
-              });
-          });
-        });
-      }
-    },
-
-    // 초기화
-    init: async function () {
-      const defaultJobGroupData = await this.$getDefaultJobGroup();
-
-      this.splitInfo = _.cloneDeep(defaultJobGroupData.customSplitter);
-      this.splitInfo_dumy = _.cloneDeep(defaultJobGroupData.customSplitter);
-
-      this.splitInfo.layout_nm = "split_0";
-      this.splitInfo.se = "splitter";
-      this.splitInfo.position_idx = 1;
-      this.splitInfo.depth = 0;
-      this.splitInfo.styleSheet.borderStyle = _.cloneDeep(
-        defaultJobGroupData.borderStyle
-      );
-      this.splitInfo.styleSheet.background_style = _.cloneDeep(
-        defaultJobGroupData.background_style
-      );
-
-      this.splitInfo_dumy.layout_nm = "dumy";
-      this.splitInfo_dumy.se = "splitter";
-      this.splitInfo_dumy.children.push(this.splitInfo);
-
-      this.selectLayout = this.splitInfo;
-      // 데이터 관리 초기화
-      this.diagramList = [];
-    },
-    initClickEvent: async function () {
-      if (
-        await this.$showConfirm(
-          "초기화",
-          "초기화 하시겠습니까? 데이터가 모두 삭제됩니다."
-        )
-      ) {
-        this.init();
-      }
-    },
-    // 축 데이터 가져오기
-    handlerAxisData: function (axis) {
-      let vm = this;
-      let changeData = _.cloneDeep(vm.selectLayout.component.component_itm);
-
-      changeData.categoryAxis = axis.dataX;
-      changeData.valueAxis = axis.dataY;
-
-      // 축 데이터를 이용한 차트 데이터 생성
-      changeData.data_list = chartDataTransform.createData(
-        vm.rowData,
-        changeData.categoryAxis,
-        changeData.valueAxis,
-        "null"
-      );
-
-      vm.selectLayout.component.component_itm = changeData;
-      // 선택한 데이터 테이블 정보 컴포넌트에 저장
-      //같은 데이터 테이블 정보가 있는지 확인하고 없으면 넣고 있으면 넣지 않음
-      let selectedJob = this.clickDiagramInfo;
-      this.selectLayout.component.jobInfo[0] = selectedJob;
-      // if(this.createChartData.);
-    },
-    // 다이어그램 데이터 테이블 정보 가져오기
-    getDiagramDataInfo: function (data) {
-      this.clickDiagramInfo = data;
-      if (data.dataTable != null) {
-        this.rowData = data.dataTable.rowData;
-      }
-    },
-    changeSelectCal: function (cal) {
-      let changeData = _.cloneDeep(this.selectLayout.component.component_itm);
-
-      changeData.chart_cal = cal;
-      this.selectLayout.component.component_itm = changeData;
-    },
-    getCustomData: function () {
-      const vm = this;
-      this.isUpdatePage = true;
-
-      axios({
-        url: "/custom/customPageUpdateSelect",
-        method: "post",
-        headers: {
-          "Content-Type": "application/json; charset=UTF-8",
-        },
-        // 객체 형태로 감싸서 데이터 전송
-        data: { page_id: vm.pageId },
-      })
-        .then(function (response) {
-          let resPageInfo = response.data.resultData.pageInfo;
-          let resdiagramList = response.data.resultData.diagramList;
-          let resSplitterInfo = response.data.resultData.splitterInfo;
-
-          vm.customTitle = resPageInfo.ttl;
-          vm.customComment = resPageInfo.cn;
-
-          vm.splitInfo = resSplitterInfo;
-
-          vm.splitInfo_dumy.layout_nm = "dumy";
-          vm.splitInfo_dumy.se = "splitter";
-          vm.splitInfo_dumy.children.push(vm.splitInfo);
-
-          vm.diagramList = resdiagramList;
-
-          vm.selectLayout = vm.splitInfo;
-          vm.isLoading = false;
-          vm.public_at = resPageInfo.public_at;
-          vm.getSelectLayoutChart();
-        })
-        .catch(function (error) {
-          vm.isLoading = false;
-          vm.$showAlert(
-            "데이터 현황관리 차트 연결",
-            "데이터 현황관리 차트 연결 오류, 관리자에게 문의하세요."
-          );
-          vm.$router.go(-1); // 오류 시 이전 페이지로 이동
-        });
-    },
-    // 레이아웃 삭제
-    deleteSplitterLayout: async function () {
-      if (!this.selectLayout.layout_nm) {
-        this.$showAlert("메세지", "삭제할 레이아웃이 없습니다.");
-        return;
-      }
-      if (!this.selectLayout.parents_splitter_id) {
-        this.$showAlert("메세지", "최상위 레이아웃은 삭제할 수 없습니다.");
-        return;
-      }
-      if (
-        await this.$showConfirm(
-          "레이아웃 삭제",
-          "선택한 레이아웃을 삭제하시겠습니까?"
-        )
-      ) {
-        let parentLayout = this.findParentLayout(
-          this.splitInfo,
-          this.selectLayout.parents_splitter_id
-        );
-        //선택한 레이아웃의 형제정보 찾기
-        let otherIdx = parentLayout.children.findIndex(
-          (item) => item.layout_nm !== this.selectLayout.layout_nm
-        );
-        let otherSplit = parentLayout.children[otherIdx];
-        //otherSplit의 component, children 정보 가져오기
-        let otherComponent = otherSplit.component;
-        let otherChildren = otherSplit.children;
-        let layoutSize1 = otherSplit.layout_size1;
-        let layoutSize2 = otherSplit.layout_size2;
-        let layoutType = otherSplit.layout_type;
-        let se = otherSplit.se;
-        let size = otherSplit.size;
-        let styleSheet = otherSplit.styleSheet;
-
-        // 부모 레이아웃에 다른 split의 정보 넣어 주기
-        parentLayout.component = otherComponent;
-        parentLayout.children = otherChildren;
-        parentLayout.layout_size1 = layoutSize1;
-        parentLayout.layout_size2 = layoutSize2;
-        parentLayout.layout_type = layoutType;
-        parentLayout.se = se;
-        parentLayout.size = size;
-        parentLayout.styleSheet = styleSheet;
-
-        // 자식에게서 가져온 자식의 자식 정보의 부모 정보 변경
-        if (parentLayout.children.length > 0) {
-          parentLayout.children.forEach((item) => {
-            item.parents_splitter_id = parentLayout.layout_nm;
-          });
-        }
-      }
-    },
-
-    // 삭제할 레이아웃의 부모 레이아웃을 찾는 재귀 함수
-    findParentLayout: function (splitInfo, selectParentLayoutId) {
-      const vm = this;
-
-      // splitInfo가 배열인 경우 각 요소에 대해 재귀적으로 함수를 호출
-      if (Array.isArray(splitInfo)) {
-        for (let i = 0; i < splitInfo.length; i++) {
-          let result = this.findParentLayout(
-            splitInfo[i],
-            selectParentLayoutId
-          );
-          if (result) return result; // 일치하는 부모 레이아웃을 찾으면 반환
-        }
-      } else {
-        // 현재 splitInfo 객체의 layout_nm이 찾고자 하는 selectParentLayoutId와 일치하는지 확인
-        if (splitInfo.layout_nm === selectParentLayoutId) {
-          return splitInfo; // 일치하는 경우 현재 객체 반환
-        }
-
-        // 현재 객체의 자식들에 대해 재귀적으로 탐색
-        if (splitInfo.children && splitInfo.children.length > 0) {
-          return this.findParentLayout(
-            splitInfo.children,
-            selectParentLayoutId
-          );
-        }
-      }
-
-      // 일치하는 요소를 찾지 못한 경우
-      return null;
-    },
-    moveBack: async function () {
-      if (await this.$showConfirm("이전으로", "이전으로 돌아가시겠습니까?")) {
-        this.$router.go(-1);
-      }
-    },
-    updateSplitInfo: function (splitInfo) {
-      this.splitInfo = splitInfo;
-    },
-  },
-  watch: {
-    splitInfo: {
-      handler: function (newVal, oldVal) {
-        this.splitInfo_dumy = _.cloneDeep(
-          this.$getDefaultJobGroup().customSplitter
-        );
-        this.splitInfo_dumy.layout_nm = "dumy";
-        this.splitInfo_dumy.se = "splitter";
-        this.splitInfo_dumy.children.push(newVal);
-      },
-      deep: true,
-    },
-    splitInfo_dumy: { deep: true },
-  },
-  components: {
-    SplitterLayout,
-    JobContainer: JobContainer,
-    SvgIcon: SvgIcon,
-    TreeItem: TreeItem,
-    BackgroundOption: BackgroundOption,
-    CellOption: CellOption,
-    FontOption: FontOption,
-    StyleSheet: StyleSheet,
-    TitleStyleSheet: TitleStyleSheet,
-    VueFlowZoneGroup: VueFlowZoneGroup,
-  },
-  created: function () {
-    this.pageId = this.$route.query.page_id;
-    this.jobGroup = this.$getDefaultJobGroup().jobGroup;
-    this.activeTab = "pageInfo";
-
-    if (this.pageId != null && this.pageId != undefined) {
-      this.getCustomData(); // 기존 데이터 가져오기
-    } else {
-      this.init();
-    }
-  }
-};
-</script>
-<style scoped>
-#splitter-container {
-  width: 100%;
-  height: calc(100% - 50px);
-  margin-bottom: 20px;
-}
-
-#SplitterLayout {
-  width: 100%;
-  height: 100%;
-}
-
-#splitter-container.active {
-  border: 3px dotted red;
-}
-
-.padding-1 {
-  padding: 1rem;
-}
-
-#customComment {
-  height: 99px;
-}
-
-.table-padding {
-  padding: 15px 0;
-}
-
-.paletteLi {
-  display: inline-block;
-  margin-right: 10px;
-}
-
-.img-wrap {
-  position: relative;
-}
-
-.img-wrap:hover .img-hover {
-  display: flex;
-}
-
-.img-wrap .img-hover {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-color: #213f9999;
-  display: none;
-  align-items: center;
-  justify-content: center;
-}
-
-.img-wrap .img-hover span {
-  padding: 10px;
-  text-align: center;
-  font-size: 1.5em;
-  color: #FFFFFF;
-  word-break: keep-all;
-}
-</style>(파일 끝에 줄바꿈 문자 없음)
client/views/pages/custom/CustomSelectList.vue
--- client/views/pages/custom/CustomSelectList.vue
+++ client/views/pages/custom/CustomSelectList.vue
@@ -5,68 +5,100 @@
       <PageNavigation />
     </div>
     <div class="content-wrap">
-      <div class="content content-box flex100">
-        <div class="column-list">
-          <div class="search-bar">
-            <div class="flex justify-end align-center">
-              <select name="searchItem" id="searchItem" class="square-select" v-model="search_data.key">
+      <div class="content-box">
+        <div class="content pd10">
+          <div class="content-titleZone flex justify-between align-center">
+            <p class="box-title"></p>
+            <div class="search-bar flex justify-end align-center">
+              <select
+                name="searchItem"
+                id="searchItem"
+                class="square-select"
+                v-model="search_data.key"
+              >
                 <option value="ttl">제목</option>
                 <option value="cn">내용</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="search_data.value" v-on:keyup.enter="customListAllSelect()" />
-                  <button class="square-button blue-btn flex10" @click="customListAllSelect()">
-                    <svg-icon type="mdi" :path="searchPath" class="square-icon"></svg-icon>
-                  </button>
-                </div>
+                <input
+                  type="text"
+                  class="square-input"
+                  placeholder="Search"
+                  v-model="search_data.value"
+                  v-on:keyup.enter="customListAllSelect()"
+                />
+                <button
+                  class="square-button blue-btn"
+                  @click="customListAllSelect()"
+                >
+                  <svg-icon
+                    type="mdi"
+                    :path="searchPath"
+                    class="square-icon"
+                  ></svg-icon>
+                </button>
               </div>
             </div>
           </div>
           <div class="table-zone">
             <ul class="gall-list flex justify-start align-center">
-              <li v-for="(item, index) in customList" :key="index" class="gall-list flex25" @click="customUpdatePage(item.page_id)">
-                <a href="">
-                  <div class="gall-img">
-                    <img :src="item.preview_img_path" alt="" />
-                  </div>
-                  <div class="gall-info">
-                    <p class="gall-title">{{ item.ttl }}</p>
-                    <p class="gall-detail">{{ item.cn }}</p>
-                  </div>
-                </a>
+              <li
+                v-for="(item, index) in customList"
+                :key="index"
+                class="gall-list flex25"
+                @click="customUpdatePage(item.page_id)"
+              >
+                <div class="gall-img">
+                  <img :src="item.preview_img_path" />
+                </div>
+                <div class="gall-info">
+                  <p class="gall-title">{{ item.ttl }}</p>
+                  <p class="gall-detail">{{ item.cn }}</p>
+                </div>
               </li>
             </ul>
+            <PaginationButton
+              v-model:currentPage="search.currentPage"
+              :perPage="search.perPage"
+              :totalCount="search.totalRows"
+              :maxRange="5"
+              :click="customListAllSelect"
+            />
           </div>
-          <PaginationButton v-model:currentPage="search.currentPage" :perPage="search.perPage" :totalCount="search.totalRows" :maxRange="5" :click="customListAllSelect" />
+          <div class="flex justify-end align-center">
+            <button class="blue-btn small-btn" @click="customInsertPage">
+              페이지 등록
+            </button>
+          </div>
         </div>
-      </div>
-      <div class="flex justify-end align-center">
-        <button class="blue-btn small-btn" @click="customInsertPage">페이지 등록</button>
       </div>
     </div>
   </div>
 </template>
 <script>
-import PageNavigation from "../../component/PageNavigation.vue";
-import PaginationButton from "../../component/PaginationButton.vue";
+import axios from "axios";
+// icon용 svg import
 import SvgIcon from "@jamescoyle/vue-icon";
 import { mdiMagnify } from "@mdi/js";
-import axios from "axios";
+// 컴포넌트 import
+import PageNavigation from "../../component/PageNavigation.vue";
+import PaginationButton from "../../component/PaginationButton.vue";
 
 export default {
   data() {
     return {
+      // svg
+      searchPath: mdiMagnify,
+
       search: _.cloneDeep(this.$getDefaultSerchVO()),
       search_data: _.cloneDeep(this.$getDefaultSerchItem("ttl", "String")),
-      searchPath: mdiMagnify,
       inputValue: null,
       customList: [],
     };
   },
   methods: {
     customInsertPage: function () {
-      this.$router.push({ path: "/customInsertDev.page", query: {} });
+      this.$router.push({ path: "/insertDataAnalytics.page", query: {} });
     },
     customUpdatePage: function (pageId) {
       this.$router.push({
@@ -80,8 +112,8 @@
       let check = false;
       let authList = vm.$store.state.loginUser.user_auth;
       for (let auth of authList) {
-        if (auth == 'ROLE_ADMIN') {
-          check = true
+        if (auth == "ROLE_ADMIN") {
+          check = true;
           break;
         }
       }
@@ -92,7 +124,7 @@
           type: "string",
           value: check + "/" + vm.$store.state.loginUser.user_id,
           value2: vm.$store.state.loginUser.dept_code,
-        })
+        });
       }
 
       axios({
client/views/pages/custom/CustomSelectOne.vue
--- client/views/pages/custom/CustomSelectOne.vue
+++ client/views/pages/custom/CustomSelectOne.vue
@@ -82,7 +82,7 @@
 <script>
 import SvgIcon from "@jamescoyle/vue-icon";
 import axios from "axios";
-import SplitterLayout from "../../component/SplitterLayoutDev.vue";
+import SplitterLayout from "../../component/SplitterLayout.vue";
 
 export default {
   data() {
@@ -161,7 +161,7 @@
     },
     pageUpdate() {
       this.$router.push({
-        path: "/customInsertDev.page",
+        path: "/insertDataAnalytics.page",
         query: { page_id: this.page_id },
       });
     },
 
client/views/pages/custom/InsertDataAnalytics.vue (added)
+++ client/views/pages/custom/InsertDataAnalytics.vue
@@ -0,0 +1,1197 @@
+<template>
+  <div class="container">
+    <div class="page-titleZone flex justify-between align-center align-start">
+      <p class="main-title flex80"> 데이터 활용관리 {{ !pageId ? "등록" : "수정" }} </p>
+    </div>
+    <div class="content-wrap content flex100">
+      <div class="custom-tab content-box">
+        <div class="flex justify-start align-center no-gutter content-box">
+          <div class="column-nav flex5">
+            <ul>
+              <li class="cursor" v-for="(item, idx) in navList" :key="idx" @click="fnChangeTab('nav', item.id)">
+                <a :class="{ activeTab: activeTab === item.id }">
+                  <svg-icon type="mdi" class="mb5" :path="item.iconPath" />
+                  <p>{{ item.name }}</p>
+                </a>
+              </li>
+            </ul>
+          </div>
+          <div class="content-box flex justify-between align-start flex95 no-gutter">
+            <div :class="{
+              'tab-zone flex align-start': true,
+              flex30: !isTabZoneOpen,
+            }">
+              <div class="content-box" v-show="!isTabZoneOpen">
+                <!-- 페이지정보탭 -->
+                <div class="content-box tab-box pd10 overflow-y" v-show="activeTab === 'pageInfo'">
+                  <div class="content-titleZone">
+                    <div class="flex justify-between align-center">
+                      <p class="box-title">페이지 정보</p>
+                    </div>
+                  </div>
+                  <table class="form-table">
+                    <tr>
+                      <th>제목</th>
+                      <td>
+                        <input type="text" class="full-input" v-model="customTitle" />
+                      </td>
+                    </tr>
+                    <tr>
+                      <th>공개여부</th>
+                      <td>
+                        <div class="input-container flex">
+                          <label class="radio-label">
+                            <input type="radio" name="public_at" class="custom-radiobox" :value="true" v-model="public_at" />
+                            <span>공개</span>
+                          </label>
+                          <label class="radio-label">
+                            <input type="radio" name="public_at" class="custom-radiobox" :value="false" v-model="public_at" />
+                            <span>비공개</span>
+                          </label>
+                        </div>
+                      </td>
+                    </tr>
+                    <tr>
+                      <th>설명</th>
+                      <td>
+                        <textarea class="content-box" cols="30" rows="35" v-model="customComment" @input="customComment = $event.target.value"></textarea>
+                      </td>
+                    </tr>
+                  </table>
+                </div>
+                <!-- 레이아웃 및 컴포넌트 탭 -->
+                <div class="content-box flex justify-between tab-box" v-show="activeTab === 'viewSetting'">
+                  <div class="content-list layout flex50">
+                    <div class="layout-tree content-box">
+                      <div class="content-titleZone">
+                        <div class="flex justify-between align-center">
+                          <p class="box-title">레이아웃 및 컴포넌트</p>
+                          <div>
+                            <button id="horizontal-btn" @click="addSplitterLayout('horizontal')" title="수직레이아웃">
+                              <img :src="verticalImg" />
+                            </button>
+                            <button id="vertical-btn" @click="addSplitterLayout('vertical')" title="수평레이아웃">
+                              <img :src="horizontalImg" />
+                            </button>
+                            <button id="split-delete-btn" @click="deleteSplitterLayout" title="삭제">
+                              <svg-icon type="mdi" :path="trashPath" :color="'#aaa'" />
+                            </button>
+                            <button class="attribute-btn" @click="fnAttributeToggle">
+                              <svg-icon type="mdi" :path="dotPath" :color="'#aaa'" />
+                              <div class="attribute-modal" v-show="isAttributeOpen">
+                                <ul>
+                                  <li class="flex align-center">
+                                    <svg-icon type="mdi" :path="tablePath" />
+                                    <p class="ml5">테이블 추가</p>
+                                  </li>
+                                  <li class="flex align-center">
+                                    <svg-icon type="mdi" :path="chartPath" />
+                                    <p class="ml5">차트 추가</p>
+                                  </li>
+                                </ul>
+                              </div>
+                            </button>
+                          </div>
+                        </div>
+                      </div>
+                      <ul>
+                        <TreeItem :splitInfo="splitInfo" :selectLayout="currentLayout" @onChange="fnSelectLayout" />
+                      </ul>
+                    </div>
+                  </div>
+                  <div class="layout-option flex-column flex50">
+                    <div class="tabnav3">
+                      <ul class="flex justify-start align-center">
+                        <template v-for="(item, idx) of optionList" :key="idx">
+                          <li class="cursor" v-if="item.useAt" @click="fnChangeTab('opt', item.id)">
+                            <p :class="{ activeOption: activeOption === item.id }">{{ item.name }}</p>
+                          </li>
+                        </template>
+                      </ul>
+                    </div>
+                    <div v-if="activeOption == 'LAYOUT'">
+                      <StyleSheet :styleSheet="currentLayout.styleSheet" />
+                    </div>
+                    <div v-if="activeOption == 'TITLE'">
+                      <div class="table-zone">
+                        <p class="object-title mb5">타이틀 사용 여부</p>
+                        <div class="input-container flex">
+                          <label class="radio-label">
+                            <input type="radio" name="titleUseAt" class="custom-radiobox" :value="true" v-model="currentLayout.useSj">
+                            <span>사용</span>
+                          </label>
+                          <label class="radio-label">
+                            <input type="radio" name="titleUseAt" class="custom-radiobox" :value="false" v-model="currentLayout.useSj">
+                            <span>미사용</span>
+                          </label>
+                        </div>
+                        <div v-if="currentLayout.useSj">
+                          <div>
+                            <p class="object-title mb5">메인타이틀</p>
+                            <input type="text" class="full-input" v-model="currentLayout.layoutSj.main_sj" />
+                            <FontOption :fontStyle="currentLayout.layoutSj.styleSheetMain.fontStyle" :title="'메인'" />
+                          </div>
+                          <div>
+                            <p class="object-title mb5">서브타이틀</p>
+                            <input type="text" class="full-input" v-model="currentLayout.layoutSj.sub_sj" />
+                            <FontOption :fontStyle="currentLayout.layoutSj.styleSheetSub.fontStyle" :title="'서브'" />
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <div v-if="activeOption == 'COMPONENT'">
+                      <StyleSheet :styleSheet="currentLayout.styleSheet" />
+                    </div>
+                  </div>
+                </div>
+                <!-- 데이터탭 -->
+                <div class="content-box tab-box" v-show="activeTab === 'dataset'">
+                  <div class="data-set" style="border-bottom: 1px solid #ddd">
+                    <div class="content-box flex-column pd10">
+                      <div class="content-titleZone">
+                        <div class="flex justify-between align-center">
+                          <p class="box-title">데이터 목록</p>
+                          <button type="button" class="blue-border-btn small-btn" @click="fnCreateJobGroup">데이터 추가</button>
+                        </div>
+                      </div>
+                      <div class="data-list">
+                        <div v-for="(item, idx) of jobGroupList" :key="idx" @click="fnSelectJobGroup(item)">
+                          <div :class="{
+                            'item flex justify-between align-center': true,
+                            mb5: idx < jobGroupList.length - 1,
+                            selectData: item.group_nm == currentJobGroup.group_nm,
+                          }">
+                            <p class="flex align-center">
+                              <svg-icon type="mdi" class="mr5" :width="20" :height="20" :path="dataPath" />
+                              <span>{{ item.group_nm }}</span>
+                            </p>
+                            <div>
+                              <button class="blue-border-btn set-btn" @click.stop="" @click="fnModalOpen(item, idx)"> 설정 </button>
+                              <button class="red-border-btn set-btn" @click.stop="" @click="deleteDiagram(item)"> 삭제 </button>
+                            </div>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <!-- 컬럼 정보 -->
+                  <div class="column-list flex no-gutter">
+                    <div class="content-box flex50 pd10" style="border-right: 1px solid #ddd">
+                      <div class="flex100 content-box">
+                        <p class="box-title mb10">컬럼정보</p>
+                        <div class="overflow-y" style="height: calc(100% - 22px)">
+                          <template v-if="currentDataTable.columnDatas.length > 0">
+                            <div v-for="(column, idx) of this.currentDataTable.columnDatas" :key="idx" :class="{
+                              item: true,
+                              mb5: idx < currentDataTable.columnDatas.length - 1,
+                            }" draggable="true" @dragstart="startDrag($event, column, idx)">
+                              <p>
+                                <b>{{ column.displyColumnNm }}</b> ({{ column.columnNm }}) : [{{ column.dataTy }}]
+                              </p>
+                            </div>
+                          </template>
+                          <div v-else>
+                            <p style="font-size: 1.3rem"> 컬럼 데이터가 존재하지 않습니다. </p>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                    <div class="content-box flex50">
+                      <div class="content-box flex-column no-gutter pd10 overflow-y">
+                        <div class="flex30 mb10">
+                          <div class="editor-box content-box pd10">
+                            <p class="object-title mb5">항목(Category)</p>
+                            <div class="overflow-y" style="height: calc(100% - 22px)" @drop.prevent="onDrop($event, 'xdata')" @dragenter.prevent @dragover.prevent>
+                              <template v-if="dataX.length > 0">
+                                <div v-for="(column, indx) of dataX" :key="indx" :class="{
+                                  'item flex justify-between align-center': true,
+                                  mb5: idx < dataX.length - 1,
+                                }">
+                                  <p> {{ column.displyColumnNm }} : [{{ column.dataTy }}] </p>
+                                  <div>
+                                    <button class="red-border-btn set-btn" @click="deleteItem(column, 'x')"> 삭제 </button>
+                                  </div>
+                                </div>
+                              </template>
+                              <div class="item mb5" v-else>
+                                <p>NO_DATA</p>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                        <div class="flex40 mb10">
+                          <div class="editor-box content-box pd10">
+                            <div class="flex justify-between align-center">
+                              <p class="object-title mb5">값(Value)</p>
+                              <select @change="fnChangeCalc">
+                                <option v-for="(item, idx) of calcList" :key="idx" :value="item.key">{{ item.value }}</option>
+                              </select>
+                            </div>
+                            <div class="overflow-y" style="height: calc(100% - 22px)" @drop.prevent="onDrop($event, 'ydata')" @dragenter.prevent @dragover.prevent>
+                              <template v-if="dataY.length > 0">
+                                <div v-for="(column, indx) of dataY" :key="indx" :class="{
+                                  'item flex justify-between align-center': true,
+                                  mb5: idx < dataY.length - 1,
+                                }">
+                                  <p> {{ column.displyColumnNm }} : [{{ column.dataTy }}] </p>
+                                  <button class="red-border-btn set-btn" @click="deleteItem(column, 'y')"> 삭제 </button>
+                                </div>
+                              </template>
+                              <div class="item mb5" v-else>
+                                <p>NO_DATA</p>
+                              </div>
+                            </div>
+                          </div>
+                        </div>
+                        <div class="flex20 mb10">
+                          <div class="editor-box content-box pd10">
+                            <p class="object-title mb5">다음으로 색상 지정</p>
+                            <ul class="overflow-y" style="height: calc(100% - 16px)">
+                              <li class="item mb5">
+                                <p>column</p>
+                              </li>
+                            </ul>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div class="content-box flex justify-between tab-box" v-show="activeTab === 'component'">
+                  <div class="content-box overflow-y">
+                    <ul class="flex justify-center">
+                      <li v-for="(cmpnt, idx) in chartComponent" :key="idx" class="flex5 mt10 cursor" @click="
+                        fnCreateComponent(idx, cmpnt.type, cmpnt.id, cmpnt.name)
+                        ">
+                        <div :class="{
+                          'img-wrap border': true,
+                          active: this.activeIndex === idx,
+                        }">
+                          <img :src="cmpnt.src" alt="" />
+                          <div class="img-hover">
+                            <span>{{ cmpnt.name }}</span>
+                          </div>
+                        </div>
+                      </li>
+                    </ul>
+                  </div>
+                </div>
+              </div>
+              <button class="custom-toggle" :style="isTabZoneOpen ? { right: '-25px' } : { right: '-24px' }" @click="fnTabZoneToggle">
+                <svg-icon type="mdi" :path="arrowPath" :color="'#333'" />
+              </button>
+            </div>
+            <!-- 미리보기  -->
+            <div :class="{
+              'preview-zone': true,
+              flex100: isTabZoneOpen,
+              flex70: !isTabZoneOpen,
+            }">
+              <div id="splitter-container" ref="splitterContainer" :class="{
+                'splitter-panel-custom pl10': true,
+                active: clickElement === 'splitter-container',
+              }">
+                <SplitterLayout :splitInfo="splitInfo" :selectLayout="currentLayout" @onChange="fnSelectLayout" />
+              </div>
+              <div class="flex justify-end" id="buttonZone">
+                <button class="blue-btn small-btn" @click="fnInsert">
+                  <span v-if="!pageId">등록</span>
+                  <span v-else>수정</span>
+                </button>
+                <button class="blue-border-btn small-btn" @click="fnInit"> 초기화 </button>
+                <button class="darkg-btn small-btn" @click="moveBack"> 취소 </button>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- 잡그룹관리 모달 -->
+  <div class="modal-wrapper" v-if="isModalOpen">
+    <div class="modal-container">
+      <div class="modal-title flex justify-between align-center">
+        <h2>잡그룹 관리</h2>
+        <button class="close-btn" @click="fnModalClose">
+          <svg-icon type="mdi" :width="20" :height="20" :path="closePath" />
+        </button>
+      </div>
+      <div class="modal-content-monthly mb10">
+        <JobGroupManagement :jobGroup="currentJobGroup" />
+      </div>
+      <div class="modal-end flex justify-end">
+        <button class="blue-btn small-btn" @click="fnSaveJobGroup"> 저장 </button>
+        <button class="blue-border-btn small-btn" @click="fnModalClose"> 취소 </button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import _ from "lodash";
+import axios from "axios";
+// icon
+import SvgIcon from "@jamescoyle/vue-icon";
+import {
+  mdiChevronLeft,
+  mdiChevronRight,
+  mdiLandPlots,
+  mdiRhombusSplit,
+  mdiDatabaseCogOutline,
+  mdiInformationSlabCircleOutline,
+  mdiDotsVertical,
+  mdiTableLarge,
+  mdiChartLine,
+  mdiDatabase,
+  mdiTrashCanOutline,
+  mdiClose,
+} from "@mdi/js";
+import SplitterLayout from "../../component/SplitterLayout.vue";
+import TreeItem from "../../component/treeMenu/LayoutTree.vue";
+import StyleSheet from "../../component/style/StyleSheetComponent.vue";
+import BackgroundOption from "../../component/style/BackgroundOption.vue";
+import CellOption from "../../component/style/CellOption.vue";
+import FontOption from "../../component/style/FontOption.vue";
+import chartDataTransform from "../../component/chart/chartDataTransform.js";
+import html2canvas from "html2canvas";
+import DataComponent from "../../component/flowComponent/DataComponent.vue";
+import JobGroupManagement from "../../component/flowComponent/JobGroupManagement.vue";
+
+export default {
+  components: {
+    SvgIcon,
+    SplitterLayout,
+    TreeItem,
+    BackgroundOption,
+    CellOption,
+    FontOption,
+    StyleSheet,
+    DataComponent,
+    JobGroupManagement,
+  },
+
+  data() {
+    return {
+      // icon
+      closePath: mdiClose,
+      arrowPath: mdiChevronLeft,
+      dotPath: mdiDotsVertical,
+      tablePath: mdiTableLarge,
+      chartPath: mdiChartLine,
+      dataPath: mdiDatabase,
+      trashPath: mdiTrashCanOutline,
+
+      horizontalImg: require("../../../resources/img/icon/hor_b.png"),
+      verticalImg: require("../../../resources/img/icon/ver_b.png"),
+
+      navList: [
+        { id: "pageInfo", name: "페이지 정보", iconPath: mdiInformationSlabCircleOutline, },
+        { id: "viewSetting", name: "레이아웃", iconPath: mdiLandPlots },
+        { id: "component", name: "컴포넌트", iconPath: mdiRhombusSplit },
+        { id: "dataset", name: "데이터", iconPath: mdiDatabaseCogOutline },
+      ],
+      optionList: [],
+      chartComponent: [
+        {
+          src: require("../../../resources/img/chartIcon/column_chart_v.png"),
+          type: "chart",
+          id: "column_chart_v",
+          name: "세로형 막대 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/column_chart_h.png"),
+          type: "chart",
+          id: "column_chart_h",
+          name: "가로형 막대 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/clustered_chart_v.png"),
+          type: "chart",
+          id: "clustered_chart_v",
+          name: "세로형 클러스터 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/clustered_chart_h.png"),
+          type: "chart",
+          id: "clustered_chart_h",
+          name: "가로형 클러스터 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/stacked_bar_chart.png"),
+          type: "chart",
+          id: "stacked_bar_chart",
+          name: "적층 막대 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/line_chart.png"),
+          type: "chart",
+          id: "line_chart",
+          name: "선 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/mix_chart.png"),
+          type: "chart",
+          id: "mix_chart",
+          name: "혼합 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/pie_chart.png"),
+          type: "chart",
+          id: "pie_chart",
+          name: "파이 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/dounet_chart.png"),
+          type: "chart",
+          id: "dounet_chart",
+          name: "도넛 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/semicircle_chart.png"),
+          type: "chart",
+          id: "semicircle_chart",
+          name: "반원 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/bubble_chart.png"),
+          type: "chart",
+          id: "bubble_chart",
+          name: "버블 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/word_chart.png"),
+          type: "chart",
+          id: "word_chart",
+          name: "단어 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/stacked_column_chart.png"),
+          type: "chart",
+          id: "stacked_column_chart",
+          name: "세로형 적층 막대 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/node_line_chart.png"),
+          type: "chart",
+          id: "node_line_chart",
+          name: "노드 라인 차트",
+        },
+        {
+          src: require("../../../resources/img/chartIcon/liin_chart_back.png"),
+          type: "chart",
+          id: "liin_chart_back",
+          name: "라인 백그라운드 차트",
+        },
+      ],
+
+      pageId: this.$route.query.page_id,
+
+      activeTab: "pageInfo",
+      activeOption: "LAYOUT",
+
+      isModalOpen: false,
+      isTabZoneOpen: false,
+      isAttributeOpen: false,
+
+      // 최상위 레이아웃 정보 담는 객체
+      splitInfo: _.cloneDeep(this.$getDefaultJobGroup().customSplitter),
+      splitInfo_dumy: _.cloneDeep(this.$getDefaultJobGroup().customSplitter),
+
+      dragData: {},
+      dataX: [],
+      dataY: [],
+
+      // 페이지정보 탭
+      customTitle: null, // 제목
+      public_at: true, // 공개여부
+      customComment: null, // 설명
+
+      activeIndex: null,
+      createChartData: _.cloneDeep(this.$getDefaultJobGroup().componentData), // x, y축 (객체 형식으로 데이터 수정 필요)
+
+      // 레이아웃 및 컴포넌트
+      // 레이아웃
+      // 타이틀
+      layoutSj: Object.assign({}, this.$getDefaultObject().layoutSj),
+
+      // 컴포넌트
+
+
+      // 잡그룹목록
+      jobGroupList: [],
+
+      // 현재 작업 객체
+      currentLayout: {},
+      currentJobGroup: Object.assign({}, this.$getDefaultObject().jobGroup),
+      currentJobGroupIdx: null,
+      currentCalc: "default",
+      currentDataTable: Object.assign({}, this.$getDefaultObject().dataTable),
+
+      // 컬럼정보
+      calcList: [
+        { key: "default", value: "기본" },
+        { key: "sum", value: "합계" },
+        { key: "avg", value: "평균" },
+        { key: "min", value: "최솟값" },
+        { key: "max", value: "최댓값" },
+      ],
+    };
+  },
+
+  created() {
+    this.activeTab = "pageInfo";
+
+    if (this.pageId != null && this.pageId != undefined) {
+      this.getCustomData(); // 기존 데이터 가져오기
+    } else {
+      this.init();
+    }
+  },
+
+  computed: {},
+
+  watch: {},
+
+  methods: {
+    // 현재 탭 변경
+    fnChangeTab(type, current) {
+      if (type == "nav") {
+        this.activeTab = current;
+      } else if (type == "opt") {
+        this.activeOption = current;
+      }
+    },
+
+    // 탭설정패널 여닫기 버튼 동작
+    fnTabZoneToggle() {
+      this.isTabZoneOpen = !this.isTabZoneOpen;
+
+      // 아이콘 변경
+      if (this.isTabZoneOpen) {
+        this.arrowPath = mdiChevronRight;
+      } else {
+        this.arrowPath = mdiChevronLeft;
+      }
+    },
+
+    // 레이아웃 및 컴포넌트 더보기 버튼 동작
+    fnAttributeToggle() {
+      this.isAttributeOpen = !this.isAttributeOpen;
+    },
+
+    // 유효성 검사 - 컴포넌트 추가
+    validationByCreateComponent(id) {
+      if (id == "bubble_chart") {
+        this.$showAlert("메세지", "현재 지원하지 않는 컴포넌트입니다.");
+        return true;
+      }
+
+      if (this.currentLayout == null) {
+        this.$showAlert("메세지", "컴포넌트를 추가할 레이아웃을 먼저 선택해 주세요.");
+        return true;
+      }
+
+      if (this.currentLayout.children.length > 0) {
+        this.$showAlert("메세지", "선택된 레이아웃이 비어있지 않습니다.<br>선택된 레이아웃 내부를 비우거나 다른 레이아웃을 선택해 주세요.");
+        return true;
+      }
+
+      return false;
+    },
+
+    // 컴포넌트 추가
+    async fnCreateComponent(index, type, id, name) {
+      this.activeIndex = index;
+
+      // 유효성 검사
+      if (this.validationByCreateComponent(id)) {
+        return
+      };
+
+      let isCheck = await this.$showConfirm("컴포넌트 추가", name + " 컴포넌트를 추가하시겠습니까?");
+      if (isCheck) {
+        let jobGroup = _.cloneDeep(this.$getDefaultJobGroup().jobGroup);
+        let chartData = _.cloneDeep(this.$getDefaultJobGroup().componentData);
+        chartData.chart_knd = id;
+        chartData.component_nm = "ChartCmmn";
+        chartData.component_type = type;
+
+        let component = _.cloneDeep(this.$getDefaultJobGroup().component);
+        component.component_itm = chartData;
+        component.jobInfo = [];
+        component.jobInfo.push(jobGroup);
+
+        this.currentLayout.se = "component";
+        this.currentLayout.component = component;
+
+        this.getSelectLayoutChart(); // 선택한 레이아웃의 차트 정보 조회
+      }
+    },
+
+    // 레이아웃 선택
+    fnSelectLayout(layout) {
+      console.log("layout: ", layout);
+
+      this.currentLayout = layout;
+      this.getSelectLayoutChart(); // 선택한 레이아웃의 차트 정보 조회
+
+      this.optionList = [
+        { id: "LAYOUT", name: "레이아웃", useAt: true },
+        { id: "TITLE", name: "타이틀", useAt: layout.children.length == 0 },
+        { id: "COMPONENT", name: "컴포넌트", useAt: !this.$isEmpty(layout.component) },
+      ];
+
+      if (this.$isEmpty(layout.component)) {
+        this.dataX = [];
+        this.dataY = [];
+        this.currentCalc = null;
+      } else {
+        this.dataX = layout.component.component_itm.categoryAxis;
+        this.dataY = layout.component.component_itm.valueAxis;
+        this.currentCalc = layout.component.component_itm.chart_cal;
+      }
+    },
+
+    // 선택한 레이아웃의 차트 정보 조회
+    getSelectLayoutChart() {
+      if (this.currentLayout.se == "component") {
+        let chartId = this.currentLayout.component.component_itm.chart_knd;
+        if (this.$isEmpty(chartId)) {
+          this.chartComponent.forEach((item, idx) => {
+            if (item.id === chartId) {
+              this.activeIndex = idx;
+            }
+          });
+        } else {
+          this.activeIndex = null;
+        }
+      }
+    },
+
+    findLoop(item) {
+      if (item.se == this.currentLayout.se) {
+        item = this.currentLayout;
+        return;
+      } else {
+        for (let next of item.children) {
+          this.findLoop(next);
+        }
+      }
+    },
+
+    // 레이아웃 추가
+    async addSplitterLayout(type) {
+      // 유효성 검사
+      if (this.currentLayout.layout_nm == null) {
+        this.$showAlert("메세지", "선택된 레이아웃이 없습니다.");
+        return;
+      }
+
+      let isCheck = await this.$showConfirm("메세지", "선택된 레이아웃을 분할하시겠습니까?");
+      if (!isCheck) {
+        return;
+      }
+
+      let copy = _.cloneDeep(this.currentLayout);
+      copy.se = "splitter";
+      copy.parents_splitter_id = this.currentLayout.layout_nm;
+      copy.depth = this.currentLayout.depth + 1;
+      copy.indx = 0;
+      copy.layout_nm = "LAY_" + this.generateShortUUID();
+
+      let newItem = _.cloneDeep(this.$getDefaultJobGroup().customSplitter);
+      newItem.se = "splitter";
+      newItem.parents_splitter_id = this.currentLayout.layout_nm;
+      newItem.depth = this.currentLayout.depth + 1;
+      newItem.indx = 1;
+      newItem.layout_nm = "LAY_" + this.generateShortUUID();
+      newItem.styleSheet.borderStyle = _.cloneDeep(this.$getDefaultJobGroup().borderStyle);
+      newItem.styleSheet.background_style = _.cloneDeep(this.$getDefaultJobGroup().background_style);
+
+      this.currentLayout.se = "splitter";
+      this.currentLayout.useSj = false;
+      this.currentLayout.layoutSj = Object.assign({}, this.$getDefaultJobGroup.layoutSj);
+      this.currentLayout.layout_type = type;
+      this.currentLayout.layout_size1 = 50;
+      this.currentLayout.layout_size2 = 50;
+
+      this.currentLayout.component = null;
+
+      this.currentLayout.children = [];
+      this.currentLayout.children.push(copy);
+      this.currentLayout.children.push(newItem);
+    },
+
+    // 등록-수정
+    fnInsert() {
+      let vm = this;
+
+      // 유효성 검사
+      if (vm.customTitle == null || vm.customTitle == "") {
+        vm.$showAlert("메세지", "제목은 필수입력입니다.");
+        return;
+      }
+
+      if (vm.splitInfo.component == null && vm.splitInfo.children.length == 0) {
+        vm.$showAlert("메세지", "레이아웃 또는 차트를 추가해야합니다.");
+        return;
+      }
+
+      // 캡처할 요소
+      let element = document.getElementById("splitter-container");
+
+      // html2canvas 사용하여 element 캡처
+      html2canvas(element).then((canvas) => {
+        // 캔버스를 Blob 데이터로 변환
+        canvas.toBlob(function (blob) {
+          let formData = new FormData();
+
+          // 공통 데이터 설정
+          formData.append("img", blob, "capture.png");
+          formData.append("ttl", vm.customTitle);
+          formData.append("cn", vm.customComment);
+          formData.append("splitInfo", vm.splitInfo);
+          formData.append("jobFlow", { jobGroup: vm.jobGroupList });
+          formData.append("public_at", vm.public_at);
+
+          // 수정 또는 등록에 따른 데이터 추가
+          let url = "/custom/insert";
+          if (!vm.pageId) {
+            formData.append("wrt_id", this.$store.state.loginUser.user_id);
+          } else {
+            url = "/custom/customUpdate";
+            formData.append("page_id", vm.pageId);
+            formData.append("mdfcn_id", this.$store.state.loginUser.user_id);
+          }
+
+          // API 호출
+          axios({
+            url: url,
+            method: "post",
+            headers: { "Content-Type": "multipart/form-data" },
+            data: formData,
+          })
+            .then(function (response) {
+              if (response.data.checkMessage.success) {
+                const successMsg = vm.pageId ? "페이지 수정에 성공하였습니다." : "페이지 등록에 성공하였습니다.";
+                vm.$showAlert("메세지", successMsg);
+
+                const pageId = vm.pageId || response.data.resultData.pageId;
+                vm.$router.push({
+                  path: "/customSelectOne.page",
+                  query: { page_id: pageId },
+                });
+              } else {
+                const failMsg = vm.pageId ? "수정에 실패하였습니다." : "등록에 실패하였습니다.";
+                vm.$showAlert("메세지", failMsg);
+              }
+            })
+            .catch(function (error) {
+              if (error.response && error.response.data && error.response.data.code == "COMPONENT_DATA_NOT_FOUND") {
+                vm.$showAlert("메세지", "데이터를 추가하지 않은 컴포넌트가 존재합니다.");
+              } else {
+                vm.$showAlert("메세지", "요청 처리 중 오류가 발생했습니다.");
+              }
+            });
+        });
+      });
+    },
+
+    // 아이디 만들기
+    generateShortUUID() {
+      const fullUUID = crypto.randomUUID();
+      return fullUUID.replace(/-/g, '').substring(0, 8);
+    },
+
+    // 초기화
+    async init() {
+      this.splitInfo = _.cloneDeep(this.$getDefaultJobGroup().customSplitter);
+      this.splitInfo.se = "splitter";
+      this.splitInfo.depth = 0;
+      this.splitInfo.layout_nm = "LAY_" + this.generateShortUUID();
+      this.splitInfo.position_idx = 1;
+      this.splitInfo.styleSheet.borderStyle = Object.assign({}, this.$getDefaultJobGroup().borderStyle);
+      this.splitInfo.styleSheet.background_style = Object.assign({}, this.$getDefaultJobGroup().background_style);
+
+      this.currentLayout = this.splitInfo;
+
+      // 데이터 관리 초기화
+      this.jobGroupList = [];
+    },
+
+    // 초기화 버튼 동작
+    async fnInit() {
+      let isCheck = await this.$showConfirm("초기화", "초기화 하시겠습니까? 데이터가 모두 삭제됩니다.");
+      if (isCheck) {
+        this.init();
+      }
+    },
+
+    // 컬럼정보 비우기
+    fnInitColInfo() {
+      this.currentJobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
+    },
+
+    // 값 계산법 변경
+    fnChangeCalc(value) {
+      this.currentLayout.component.component_itm.chart_cal = value;
+    },
+
+    // 기존 데이터 가져오기
+    getCustomData() {
+      const vm = this;
+      axios({
+        url: "/custom/customPageUpdateSelect",
+        method: "post",
+        headers: { "Content-Type": "application/json; charset=UTF-8" },
+        data: { page_id: vm.pageId },
+      })
+        .then(function (response) {
+          let resPageInfo = response.data.resultData.pageInfo;
+          let resjobGroupList = response.data.resultData.jobGroupList;
+          let resSplitterInfo = response.data.resultData.splitterInfo;
+
+          vm.customTitle = resPageInfo.ttl;
+          vm.customComment = resPageInfo.cn;
+
+          vm.splitInfo = resSplitterInfo;
+
+          vm.splitInfo_dumy.layout_nm = "dumy";
+          vm.splitInfo_dumy.se = "splitter";
+          vm.splitInfo_dumy.children.push(vm.splitInfo);
+
+          vm.jobGroupList = resjobGroupList;
+
+          vm.currentLayout = vm.splitInfo;
+          vm.public_at = resPageInfo.public_at;
+          vm.getSelectLayoutChart();
+        })
+        .catch(function (error) {
+          vm.$showAlert(
+            "데이터 현황관리 차트 연결",
+            "데이터 현황관리 차트 연결 오류, 관리자에게 문의하세요."
+          );
+          vm.moveBack();
+        });
+    },
+
+    async deleteSplitterLayout() {
+      if (!this.currentLayout.layout_nm) {
+        this.$showAlert("메세지", "삭제할 레이아웃이 없습니다.");
+        return;
+      }
+      if (!this.currentLayout.parents_splitter_id) {
+        this.$showAlert("메세지", "최상위 레이아웃은 삭제할 수 없습니다.");
+        return;
+      }
+      if (
+        await this.$showConfirm(
+          "레이아웃 삭제",
+          "선택한 레이아웃을 삭제하시겠습니까?"
+        )
+      ) {
+        let parentLayout = this.findParentLayout(
+          this.splitInfo,
+          this.currentLayout.parents_splitter_id
+        );
+        //선택한 레이아웃의 형제정보 찾기
+        let otherIdx = parentLayout.children.findIndex(
+          (item) => item.layout_nm !== this.currentLayout.layout_nm
+        );
+        let otherSplit = parentLayout.children[otherIdx];
+        //otherSplit의 component, children 정보 가져오기
+        let otherComponent = otherSplit.component;
+        let otherChildren = otherSplit.children;
+        let layoutSize1 = otherSplit.layout_size1;
+        let layoutSize2 = otherSplit.layout_size2;
+        let layoutType = otherSplit.layout_type;
+        let se = otherSplit.se;
+        let size = otherSplit.size;
+        let styleSheet = otherSplit.styleSheet;
+
+        // 부모 레이아웃에 다른 split의 정보 넣어 주기
+        parentLayout.component = otherComponent;
+        parentLayout.children = otherChildren;
+        parentLayout.layout_size1 = layoutSize1;
+        parentLayout.layout_size2 = layoutSize2;
+        parentLayout.layout_type = layoutType;
+        parentLayout.se = se;
+        parentLayout.size = size;
+        parentLayout.styleSheet = styleSheet;
+
+        // 자식에게서 가져온 자식의 자식 정보의 부모 정보 변경
+        if (parentLayout.children.length > 0) {
+          parentLayout.children.forEach((item) => {
+            item.parents_splitter_id = parentLayout.layout_nm;
+          });
+        }
+      }
+    },
+
+    findParentLayout(splitInfo, selectParentLayoutId) {
+      // splitInfo가 배열인 경우 각 요소에 대해 재귀적으로 함수를 호출
+      if (Array.isArray(splitInfo)) {
+        for (let i = 0; i < splitInfo.length; i++) {
+          let result = this.findParentLayout(
+            splitInfo[i],
+            selectParentLayoutId
+          );
+          if (result) return result; // 일치하는 부모 레이아웃을 찾으면 반환
+        }
+      } else {
+        // 현재 splitInfo 객체의 layout_nm이 찾고자 하는 selectParentLayoutId와 일치하는지 확인
+        if (splitInfo.layout_nm === selectParentLayoutId) {
+          return splitInfo; // 일치하는 경우 현재 객체 반환
+        }
+
+        // 현재 객체의 자식들에 대해 재귀적으로 탐색
+        if (splitInfo.children && splitInfo.children.length > 0) {
+          return this.findParentLayout(
+            splitInfo.children,
+            selectParentLayoutId
+          );
+        }
+      }
+
+      // 일치하는 요소를 찾지 못한 경우
+      return null;
+    },
+
+    // 취소 (데이터활용관리 목록으로 이동)
+    async moveBack() {
+      let isCheck = await this.$showConfirm("경고", "취소할 경우 작성된 내용이 삭제됩니다.");
+      if (isCheck) {
+        this.$router.push({ path: "/customSelectList.page" });
+      }
+    },
+
+    // 잡그룹 추가
+    fnCreateJobGroup() {
+      let jobGroup = Object.assign({}, this.$getDefaultObject().jobGroup);
+      jobGroup.group_nm = "데이터" + (this.jobGroupList.length + 1);
+      this.jobGroupList.push(jobGroup);
+      this.fnSelectJobGroup(jobGroup); // 잡그룹 선택 (추가한 잡그룹 선택)
+    },
+
+    // 잡그룹 선택
+    fnSelectJobGroup(jobGroup) {
+      this.currentJobGroup = jobGroup;
+
+      // 잡그룹 내 마지막 데이터테이블 조회
+      if (!this.currentJobGroup.jobItms || this.currentJobGroup.jobItms.length === 0) {
+        this.currentDataTable = Object.assign({}, this.$getDefaultObject().dataTable);
+      } else {
+        const index = this.currentJobGroup.jobItms.length - 1;
+        this.currentDataTable = this.currentJobGroup.jobItms[index].dataTable;
+      }
+    },
+
+    // 잡그룹관리 모달 열기
+    fnModalOpen(jobGroup, idx) {
+      this.currentJobGroup = _.cloneDeep(jobGroup);
+      this.currentJobGroupIdx = idx;
+      this.isModalOpen = true;
+    },
+
+    // 잡그룹관리 모달 닫기
+    fnModalClose() {
+      this.isModalOpen = false;
+    },
+
+    // 잡그룹관리 저장
+    fnSaveJobGroup() {
+      this.jobGroupList[this.currentJobGroupIdx] = this.currentJobGroup;
+      this.fnSelectJobGroup(this.currentJobGroup); // 잡그룹 선택 (저장한 정보 선택)
+      this.fnModalClose(); // 노드설정 모달 닫기
+    },
+
+    // 드래그 이벤트
+    startDrag(event, data, idx) {
+      this.dragData = data;
+    },
+
+    // 드랍 이벤트
+    async onDrop(event, type) {
+      // 컴포넌트가 설정되지 않은 경우 경고 후 실행 취소
+      if (this.$isEmpty(this.currentLayout.component)) {
+        this.$showAlert("경고", "컴포넌트 등록 후 설정할 수 있습니다.");
+        return;
+      }
+
+      if (type == "xdata") {
+        if (this.dataX.length > 0) {
+          let isCheck = await this.$showConfirm(
+            "경고",
+            "데이터를 변경하시겠습니까?"
+          );
+          if (!isCheck) {
+            return;
+          }
+        }
+        this.dataX = []; // 초기화
+        this.dataX.push(this.dragData);
+      } else if (type == "ydata") {
+        if (this.dragData.dataTy === "STRING") {
+          this.$showAlert(
+            "메세지",
+            "값(Value)에는 숫자 데이터만 사용 가능합니다."
+          );
+          return;
+        }
+        this.dataY.push(this.dragData);
+      }
+
+      if (!this.$isEmpty(this.dataX)) {
+        this.currentLayout.component.component_itm.categoryAxis = this.dataX;
+      }
+      if (!this.$isEmpty(this.dataY)) {
+        this.currentLayout.component.component_itm.valueAxis = this.dataY;
+      }
+
+      // 축 데이터를 이용한 차트 데이터 생성
+      if (!this.$isEmpty(this.dataX) && !this.$isEmpty(this.dataY)) {
+        let dataList = chartDataTransform.createData(
+          this.currentDataTable.rowData,
+          this.currentLayout.component.component_itm.categoryAxis,
+          this.currentLayout.component.component_itm.valueAxis,
+          "null"
+        );
+        this.currentLayout.component.component_itm.data_list = dataList;
+      }
+      this.currentLayout.component.jobInfo[0] = this.currentJobGroup;
+    },
+
+    // 아이템 삭제
+    deleteItem(column, category) {
+      if (category == "x") {
+        for (let i = 0; i < this.dataX.length; i++) {
+          if (this.dataX[i].orginlColumnNm == column.orginlColumnNm) {
+            this.dataX.splice(i, 1);
+          }
+        }
+      } else if (category == "y") {
+        for (let i = 0; i < this.dataY.length; i++) {
+          if (this.dataY[i].orginlColumnNm == column.orginlColumnNm) {
+            this.dataY.splice(i, 1);
+          }
+        }
+      }
+    },
+
+    // 잡그룹 삭제
+    async deleteDiagram(item) {
+      let isCheck = await this.$showConfirm(
+        "삭제",
+        "선택한 데이터를 삭제하시겠습니까?\n관련된 데이터도 함께 삭제됩니다."
+      );
+      if (!isCheck) {
+        return;
+      }
+
+      // splitInfo에 적용했던 데이터가 있는지 확인하여 삭제 (재귀)
+      if (this.splitInfo.children.length > 0) {
+        this.RecursiveFunc(this.splitInfo.children, item);
+      }
+
+      if (this.splitInfo.component != null) {
+        if (this.splitInfo.component.jobInfo[0].group_nm == item.group_nm) {
+          this.splitInfo.component = null;
+          this.splitInfo.se = "splitter";
+        }
+      }
+
+      this.jobGroupList = this.jobGroupList.filter(
+        (jobItem) => jobItem !== item
+      );
+
+      // 초기화
+      this.dragData = {};
+      this.dataX = [];
+      this.dataY = [];
+    },
+
+    // 재귀함수
+    RecursiveFunc(children, item) {
+      for (let i = 0; i < children.length; i++) {
+        if (children[i].children.length > 0) {
+          this.RecursiveFunc(children[i].children, item);
+        } else {
+          if (children[i].component != null) {
+            // children[i].jobInfo 와 item이 같은지 확인하여 삭제
+            if (children[i].component.jobInfo[0].group_nm == item.group_nm) {
+              children[i].component = null;
+              children[i].se = "splitter";
+            }
+          }
+        }
+      }
+    },
+  },
+};
+</script>
+<style scoped>
+#splitter-container {
+  width: 100%;
+  height: calc(100% - 50px);
+  margin-bottom: 20px;
+}
+
+#SplitterLayout {
+  width: 100%;
+  height: 100%;
+}
+
+#splitter-container.active {
+  border: 3px dotted red;
+}
+
+.padding-1 {
+  padding: 1rem;
+}
+
+#customComment {
+  height: 99px;
+}
+
+.table-padding {
+  padding: 15px 0;
+}
+
+.paletteLi {
+  display: inline-block;
+  margin-right: 10px;
+}
+
+.img-wrap {
+  position: relative;
+}
+
+.img-wrap:hover .img-hover {
+  display: flex;
+}
+
+.img-wrap .img-hover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: #213f9999;
+  display: none;
+  align-items: center;
+  justify-content: center;
+}
+
+.img-wrap .img-hover span {
+  padding: 10px;
+  text-align: center;
+  font-size: 1.5em;
+  color: #ffffff;
+  word-break: keep-all;
+}
+
+.selectData {
+  border: solid 2px #5d5c5c;
+  color: #ffffff;
+  background-color: #ff9e29;
+}
+</style>(파일 끝에 줄바꿈 문자 없음)
client/views/pages/data/filemanger/FileManagementMain.vue
--- client/views/pages/data/filemanger/FileManagementMain.vue
+++ client/views/pages/data/filemanger/FileManagementMain.vue
@@ -457,8 +457,8 @@
 </template>
 <script>
 import axios from "axios";
-import TreeItem from "../../../component/FileTree.vue";
-import TreeModal from "../../../component/FileTreeModal.vue";
+import TreeItem from "../../../component/treeMenu/FileTree.vue";
+import TreeModal from "../../../component/treeMenu/FileTreeModal.vue";
 import SvgIcon from "@jamescoyle/vue-icon";
 import NodeSetupModalDA from "../../../component/modal/NodeSetupModalDA.vue";
 import EhojoConnection from "../../../component/connection/EhojoConnection.vue";
client/views/pages/integrated/department/DeptHostList.vue
--- client/views/pages/integrated/department/DeptHostList.vue
+++ client/views/pages/integrated/department/DeptHostList.vue
@@ -139,7 +139,7 @@
 // 컴포넌트 import
 import PaginationButton from "../../../component/PaginationButton.vue";
 import HostDrctryListModal from "../../../component/modal/HostDrctryListModal.vue";
-import TreeModal from "../../../component/FileTreeModal.vue";
+import TreeModal from "../../../component/treeMenu/FileTreeModal.vue";
 
 export default {
   components: { SvgIcon, PaginationButton, HostDrctryListModal, TreeModal },
Add a comment
List