Procházet zdrojové kódy

2024-02-21 conn-hs page update

junggilpark před 1 rokem
rodič
revize
0ed7856b6d

+ 28 - 2
src/main/java/com/its/web/controller/admin/AdminController.java

@@ -2,6 +2,8 @@ package com.its.web.controller.admin;
 
 import com.its.web.service.notice.NoticeService;
 import com.its.web.service.popup.PopupService;
+import com.its.web.service.traffic.IntersectionService;
+import com.its.web.service.traffic.TrafficService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
@@ -26,8 +28,10 @@ import java.io.IOException;
 public class AdminController {
     private final NoticeService noticeService;
     private final PopupService popupService;
+    private final TrafficService trafficService;
+    private final IntersectionService ixrService;
 
-    @ApiOperation(value = "00.로그인")
+    @ApiOperation(value = "00.공통 - 01.로그인")
     @GetMapping("")
     public String login(HttpSession session,
                         Model model,
@@ -37,7 +41,19 @@ public class AdminController {
         return "admin/login";
     }
 
-    @ApiOperation(value = "00.메인화면")
+    @ApiOperation(value = "00.공통 - 02.로그아웃")
+    @GetMapping("/logout")
+    public void logout(HttpSession session, HttpServletResponse res) {
+        try {
+            session.invalidate();
+
+            res.sendRedirect("/phits");
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @ApiOperation(value = "00.공통 - 03.메인화면")
     @GetMapping("/main")
     public String adminMain(Model model) {
         model.addAttribute("notice", this.noticeService.findMainNotice(5));
@@ -99,4 +115,14 @@ public class AdminController {
         model.addAttribute("selected", "popup");
         return "admin/popup-write";
     }
+
+    @ApiOperation(value = "04.CCTV 관리")
+    @GetMapping("/cctv-list")
+    public String adminCctvManagement(Model model) {
+        model.addAttribute("selected", "cctv");
+        model.addAttribute("cctv", trafficService.findAllCctv());
+        model.addAttribute("intersection", ixrService.findAllIntersection());
+        return "admin/cctv";
+    }
+
 }

+ 4 - 5
src/main/java/com/its/web/controller/common/CommonController.java

@@ -7,12 +7,11 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
+import javax.annotation.Nullable;
 import java.util.List;
+import java.util.Map;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -31,6 +30,6 @@ public class CommonController {
     @ApiOperation(value = "접속자 통계(TB_WWW_CONN_HS)", response = ConnStatisticsDto.class, responseContainer = "ArrayList")
     @PostMapping(value = "/conn-statistics", produces = {"application/json; charset=utf8"})
     @ResponseBody
-    public List<ConnStatisticsDto> getConnStatistics() {return this.service.getConnStatistics();}
+    public List<ConnStatisticsDto> getConnStatistics(@Nullable @RequestParam Map<String, String> paramMap) {return this.service.getConnStatistics(paramMap);}
 
 }

+ 2 - 7
src/main/java/com/its/web/interceptor/ConnectHistory.java

@@ -14,8 +14,6 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import java.net.InetAddress;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
 
 @Slf4j
 @RequiredArgsConstructor
@@ -30,14 +28,11 @@ public class ConnectHistory implements HandlerInterceptor {
         String uri = request.getRequestURI();
         String[] ipAddressArr = new String[]{"127.0.0.1", "localhost", "192.168.20.46"};
         HttpSession session = request.getSession();
-        if (session.getAttribute(hostIp) == null) {
+        if (session.getAttribute(hostIp) == null && !uri.contains("/phits")) {
             log.info("Connect Ip Address : {}, UUID : {}", hostIp, session.getId());
             session.setAttribute(hostIp, session.getId());
-            Map<String, String> paramMap = new HashMap<>();
-            paramMap.put("connIpAddr", hostIp);
-            paramMap.put("connUuid", session.getId());
             try {
-                service.insertConnHs(paramMap);
+                service.insertConnHs();
             }
             catch (DataAccessException e) {
                 log.error("접속 이력 등록에 실패하였습니다.");

+ 5 - 3
src/main/java/com/its/web/mapper/its/common/CommonMapper.java

@@ -14,9 +14,11 @@ public interface CommonMapper {
 
     List<CodeDto> findDayList();
 
-    int insertConnHs(Map<String, String> paramMap);
+    int insertConnHs();
 
-    List<ConnStatisticsDto> getConnStatistics();
+    List<ConnStatisticsDto> getConnStatistics(Map<String, String> paramMap);
 
-    Integer getCountConnHs(Map<String, String> paramMap);
+    List<ConnStatisticsDto> getConnMonthStatistics(Map<String, String> paramMap);
+
+    List<ConnStatisticsDto> getConnWeekStatistics(Map<String, String> paramMap);
 }

+ 11 - 8
src/main/java/com/its/web/service/common/CommonService.java

@@ -37,19 +37,22 @@ public class CommonService {
      * 접속자 조회 통계
      * @return List<ConnStatisticsDto>
      */
-    public List<ConnStatisticsDto> getConnStatistics() { return this.mapper.getConnStatistics(); }
+    public List<ConnStatisticsDto> getConnStatistics(Map<String, String> paramMap) {
+        String type = paramMap.get("type");
+        if ("week".equals(type)) {
+            return this.mapper.getConnWeekStatistics(paramMap);
+        }
+        else if ("month".equals(type)) {
+            return this.mapper.getConnMonthStatistics(paramMap);
+        }
+        return this.mapper.getConnStatistics(paramMap);
+    }
 
     /**
      * 접속자 이력 등록
      * @return DB 등록 수
      */
-    public int insertConnHs(Map<String,String> paramMap) { return this.mapper.insertConnHs(paramMap); }
+    public int insertConnHs() { return this.mapper.insertConnHs(); }
 
 
-    /**
-     * 접속 이력 있는지 확인
-     * @param paramMap
-     * @return
-     */
-    public Integer getCountConnHs(Map<String,String> paramMap) {return this.mapper.getCountConnHs(paramMap);}
 }

+ 43 - 12
src/main/resources/mybatis/mapper/its/common/CommonMapper.xml

@@ -14,24 +14,55 @@
         WHERE CMMN_CLSF_CD = 'DTW'
     </select>
 
-    <insert id="insertConnHs" parameterType="java.util.HashMap">
-        INSERT INTO TB_WWW_CONN_HS (CONN_DT, CONN_IP_ADDR, CONN_UUID) VALUES(TO_CHAR(sysdate, 'YYYYMMDDHH24MISS'), #{connIpAddr}, #{connUuid})
+    <insert id="insertConnHs">
+        MERGE INTO TB_WWW_CONN_HS A USING DUAL
+           ON (A.CONN_DAY = TO_CHAR(sysdate, 'YYYYMMDD'))
+         WHEN MATCHED THEN UPDATE SET A.CONN_CNT = (A.CONN_CNT + 1)
+         WHEN NOT MATCHED THEN INSERT (CONN_DAY, CONN_CNT) VALUES (TO_CHAR(sysdate, 'YYYYMMDD'), 1)
     </insert>
 
-    <select id="getConnStatistics" resultType="com.its.web.dto.common.ConnStatisticsDto">
+    <select id="getConnStatistics" resultType="com.its.web.dto.common.ConnStatisticsDto" parameterType="java.util.HashMap">
         SELECT
-            TO_CHAR(TO_DATE(A.DAYS, 'YYYY-MM-DD'), 'YYYY-MM-DD') AS conn_dt,
-            count(B.CONN_IP_ADDR) AS conn_cnt
-          FROM ( SELECT TO_CHAR(SYSDATE, 'YYYYMMDD') DAYS FROM DUAL
+            TO_CHAR(TO_DATE(A.DAYS, 'YYYYMMDD'), 'YYYY-MM-DD') AS conn_dt,
+            NVL(B.conn_cnt, 0) AS conn_cnt
+          FROM ( SELECT #{toDate} DAYS FROM DUAL
                  UNION ALL
-                 SELECT TO_CHAR(TRUNC(TO_DATE(SYSDATE), 'DD') - LEVEL, 'YYYYMMDD') DAYS FROM DUAL
-                 CONNECT BY LEVEL <![CDATA[<=]]> 7
+                 SELECT TO_CHAR(TRUNC(TO_DATE(#{toDate}), 'DD') - LEVEL, 'YYYYMMDD') DAYS FROM DUAL
+                 CONNECT BY LEVEL <![CDATA[<=]]> (TO_DATE(#{toDate}) - TO_DATE(#{fromDate}))
                 ) A
-        LEFT OUTER JOIN TB_WWW_CONN_HS B ON A.DAYS = TO_CHAR(TO_DATE(B.conn_dt, 'YYYYMMDDHH24MISS'), 'YYYYMMDD')
-        GROUP BY A.DAYS ORDER BY A.DAYS;
+          LEFT OUTER JOIN TB_WWW_CONN_HS B
+                       ON A.DAYS = B.conn_day
+         ORDER BY conn_dt ASC
     </select>
 
-    <select id="getCountConnHs" parameterType="java.util.HashMap" resultType="java.lang.Integer">
-        SELECT COUNT(*) FROM TB_WWW_CONN_HS WHERE CONN_IP_ADDR = #{connIpAddr} AND CONN_UUID = #{connUuid}
+    <select id="getConnWeekStatistics" resultType="com.its.web.dto.common.ConnStatisticsDto" parameterType="java.util.HashMap">
+        SELECT
+            TO_CHAR(TO_DATE(A.start_dt, 'YYYYMMDD'), 'YYYY-MM-DD') || ' ~ ' || TO_CHAR(TO_DATE(A.end_dt, 'YYYYMMDD'), 'YYYY-MM-DD') AS conn_dt,
+            SUM(NVL(B.conn_cnt, 0)) AS conn_cnt
+          FROM (SELECT TO_CHAR(TRUNC(TO_DATE(#{toDate}), 'DD') - 6, 'YYYYMMDD') AS start_dt, #{toDate} AS end_dt FROM DUAL
+                UNION ALL
+                SELECT
+                    TO_CHAR(TRUNC(TO_DATE(#{toDate}), 'DD') - (7 * (LEVEL + 1) - 1), 'YYYYMMDD') AS start_dt,
+                    TO_CHAR(TRUNC(TO_DATE(#{toDate}), 'DD') - (7 * LEVEL), 'YYYYMMDD') AS end_dt
+                  FROM DUAL
+                CONNECT BY LEVEL <![CDATA[<=]]> FLOOR((TO_DATE(#{toDate}) - TO_DATE(#{fromDate})) / 7)) A
+        LEFT OUTER JOIN TB_WWW_CONN_HS B ON TO_DATE(B.conn_day, 'YYYYMMDD') BETWEEN A.start_dt and A.end_dt
+        GROUP BY TO_CHAR(TO_DATE(A.start_dt, 'YYYYMMDD'), 'YYYY-MM-DD') || ' ~ ' || TO_CHAR(TO_DATE(A.end_dt, 'YYYYMMDD'), 'YYYY-MM-DD')
+        ORDER BY TO_CHAR(TO_DATE(A.start_dt, 'YYYYMMDD'), 'YYYY-MM-DD') || ' ~ ' || TO_CHAR(TO_DATE(A.end_dt, 'YYYYMMDD'), 'YYYY-MM-DD')
     </select>
+
+    <select id="getConnMonthStatistics" resultType="com.its.web.dto.common.ConnStatisticsDto" parameterType="java.util.HashMap">
+        SELECT
+            TO_CHAR(TO_DATE(A.MONTHS, 'YYYYMM'), 'YYYY-MM') AS conn_dt,
+            SUM(NVL(B.conn_cnt, 0)) AS conn_cnt
+        FROM ( SELECT #{toDate} AS MONTHS FROM DUAL
+               UNION ALL
+               SELECT TO_CHAR(ADD_MONTHS(TO_DATE(#{toDate}, 'YYYYMM'), -LEVEL), 'YYYYMM') MONTHS FROM DUAL
+               CONNECT BY LEVEL <![CDATA[<=]]> MONTHS_BETWEEN(TO_DATE(#{toDate}, 'YYYYMM'), TO_DATE(#{fromDate}, 'YYYYMM'))
+              ) A
+        LEFT OUTER JOIN TB_WWW_CONN_HS B ON A.MONTHS = SUBSTR(B.conn_day, 0, 6)
+        GROUP BY TO_CHAR(TO_DATE(A.MONTHS, 'YYYYMM'), 'YYYY-MM')
+        ORDER BY TO_CHAR(TO_DATE(A.MONTHS, 'YYYYMM'), 'YYYY-MM')
+    </select>
+
 </mapper>

+ 325 - 0
src/main/resources/static/css/cctv.css

@@ -0,0 +1,325 @@
+
+.menu {
+    max-width: 1200px;
+    width: 100%;
+    margin: 1.5rem auto 0 auto;
+    display: flex;
+    -webkit-box-pack: center;
+    justify-content: center;
+}
+.menu > div {
+    width: 100px;
+    word-break: keep-all;
+    text-align: center;
+    line-height: 1.1;
+    font-weight: bold;
+    font-size: 18px;
+}
+
+.menu > div:not(:first-child) {
+    margin-left: 30px;
+}
+
+.menu > div.active {
+    color: rgb(51, 102, 171);
+}
+
+.menu > div:hover{
+    color: rgb(51, 102, 171);
+    cursor: pointer;
+}
+
+.mobile-menu {
+    display: none;
+    padding: 0.6rem 1rem;
+    border-top: 1px solid rgb(230, 230, 230);
+    justify-content: space-around;
+    width: 100%;
+    height: 80px;
+}
+.mobile-menu > div > div {
+    filter: grayscale(1);
+    background-size: 35px 35px;
+    width: 100%;
+    height: 35px;
+    background-position: center;
+    background-repeat: no-repeat;
+}
+.mobile-menu > div {
+    font-weight: bold;
+    padding-top : 5px;
+    font-size: 11px;
+}
+.mobile-menu > div.active > div {
+    filter: grayscale(0);
+}
+.mobile-menu > div.active {
+    color: rgb(51, 102, 171);
+}
+
+.mobile-menu > div:nth-child(1) > div {
+    background-image: url("/images/icon/menu_icon5-2.png");
+}
+
+.mobile-menu > div:nth-child(2) > div {
+    background-image: url("/images/icon/way.png");
+}
+
+.cctvWrap {
+    width: 100%;
+    height: calc(100% - 199.8px);
+    display: flex;
+    justify-content: center;
+    overflow: auto;
+}
+.cctvWrap .admin-header {
+    width: 200px;
+    height: 50px;
+    margin: 24px auto;
+    color: white;
+    text-align: center;
+    font: bold 18px / 40px NanumGothic;
+    background: url('/images/background/bg_title.png') 0px 0px / contain no-repeat transparent;
+}
+
+.cctvWrap .container {
+    max-width: 1200px;
+    width: 95%;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    min-height: 700px;
+}
+
+.cctvWrap .header {
+    padding: 2rem 0;
+    margin-bottom: 0;
+    color: rgb(51, 102, 171);
+    text-align: center;
+    border-bottom: 1px solid rgb(33, 84, 153);
+    font-size: 2rem;
+}
+
+.cctvWrap .content {
+    margin: 24px 0px;
+    padding: 30px 60px;
+    transition: all 0.3s ease 0s;
+    box-shadow: rgba(0, 0, 0, 0.15) 0px 3px 6px;
+    height: calc(100% - 101px);
+}
+.cctvWrap .tab {
+    width: 232px;
+    height: 50px;
+    align-items: center;
+    margin-bottom: 5px;
+    display: flex;
+    transition: all 0.3s ease 0s;
+    background-color: rgb(255, 255, 255);
+    box-shadow: rgba(0, 0, 0, 0.15) 0px 3px 6px;
+}
+.cctvWrap .tab > div {
+    background-color: white;
+    color: black;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+    padding : 0 5px;
+    cursor: pointer;
+}
+.cctvWrap .tab > div.active {
+    background-color: rgb(51, 102, 171);
+    color: white;
+}
+
+.cctvWrap .admin-content {
+    width: 100%;
+    height: calc(100% - 200px);
+    min-height: 500px;
+    padding: 20px;
+    transition: all 0.3s ease 0s;
+    display: flex;
+    background-color: rgb(255, 255, 255);
+    box-shadow: rgba(0, 0, 0, 0.15) 0px 3px 6px;
+}
+.cctvWrap .admin-content .right {
+    width: calc(100% - 320px);
+    height: 100%;
+    padding: 10px;
+    border : 1px solid #c3c1c1;
+}
+
+.cctvWrap .admin-content .right .title{
+    width: 100%;
+    display: flex;
+    color: red;
+}
+.cctvWrap .admin-content .right .title .toggle-button {
+    display: inline-flex;
+    align-items: center;
+    gap: 0.5rem;
+    cursor: pointer;
+    margin-left: auto;
+}
+[type="checkbox"] {
+    appearance: none;
+    position: relative;
+    border-radius: 1.25em;
+    width: 2.75em;
+    height: 1.7em;
+    background-color: rgb(204, 204, 204);
+}
+[type="checkbox"]::before {
+    content: "";
+    position: absolute;
+    left: 0;
+    width: 1.7em;
+    height: 1.7em;
+    border-radius: 50%;
+    transform: scale(0.8);
+    background-color: white;
+    transition: left 250ms linear;
+}
+[type="checkbox"]:checked::before {
+    background-color: white;
+    left: 1em;
+}
+
+[type="checkbox"]:checked {
+    background-color: rgb(51, 102, 171);
+}
+
+.cctvWrap .admin-content .left {
+    width: 300px;
+    height: 100%;
+    margin-right: 20px;
+}
+
+.cctvWrap .admin-content .left .list{
+    width: 100%;
+    height: calc(100% - 50px);
+    margin-top: 10px;
+    border: 1px solid #c3c1c1;
+    overflow: auto;
+}
+
+.cctvWrap .admin-content .left .list ul > div {
+    padding: 10px;
+}
+
+.cctvWrap .admin-content .left .list ul.active > div {
+    color: rgb(51, 102, 171);
+    font-weight: bold;
+}
+
+.cctvWrap .admin-content .left .list ul > .cmra-list > li:hover,
+.cctvWrap .admin-content .left .list ul > div:hover{
+    color: rgb(51, 102, 171);
+    font-weight: bold;
+    cursor: pointer;
+}
+.cctvWrap .admin-content .left .list ul.active > .cmra-list {
+    display: block;
+}
+
+.cctvWrap .admin-content .left .list ul > .cmra-list {
+    display: none;
+    padding-left: 50px;
+}
+
+.cctvWrap .admin-content .left .list ul > .cmra-list > li {
+    padding: 5px;
+    list-style-type: circle;
+}
+
+
+.cctvWrap .admin-content .left .title {
+    width: 100%;
+    height: 40px;
+    background-color: rgb(51, 102, 171);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: white;
+}
+
+@media (max-width: 920px) {
+    .cctvWrap {
+        height: calc(100% - 200.19px);
+    }
+
+}
+
+@media (max-height: 765px) {
+    .cctvWrap {
+        height: calc(100% - 200.19px);
+    }
+}
+
+
+@media (max-width: 720px) {
+    .cctvWrap {
+        height: calc(100% - 205.19px);
+    }
+}
+
+
+@media (max-width: 547px) {
+    .cctvWrap {
+        height: calc(100% - 216.19px);
+    }
+}
+
+@media (max-width: 420px) {
+    .mobile-menu {
+        display: flex;
+    }
+
+    .menu {
+        display:  none;
+    }
+
+    .cctvWrap {
+        height: calc(100% - 149.19px);
+        padding: 5px 0;
+    }
+
+    .cctvWrap .header {
+        font-size: 1.2rem;
+        padding: 1rem 0;
+    }
+
+
+    .cctvWrap .container {
+        min-height: 0;
+    }
+    .cctvWrap .container.view {
+        min-height: 450px;
+    }
+
+    .cctvWrap h2 {
+        font-size: 14px;
+    }
+    .cctvWrap .content a {
+        padding: 10px;
+        font-size: 13px;
+    }
+
+    .cctvWrap .content a > div:nth-child(1) {
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        width: calc(100% - 135px);
+    }
+    .item-right{
+        width : 122px;
+        font-size: 12px;
+    }
+    .item-right > div:nth-child(2) {
+        display: none;
+    }
+
+    .cctvWrap .content.view {
+        padding: 0px 20px;
+    }
+}

+ 6 - 0
src/main/resources/static/css/common.css

@@ -153,6 +153,12 @@ header #menu-modal .modal-container {
     border-radius: 1rem;
 }
 
+header .top-menu span#logout img {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+}
+
 header .sub-number {
     position: relative;
     color: rgb(234, 234, 234);

+ 44 - 2
src/main/resources/static/css/conn-statistics.css

@@ -138,7 +138,20 @@
 }
 
 .title .button {
-
+    display: inline-block;
+    width: 100px;
+    height: 40px;
+    color: rgb(255, 255, 255);
+    border: 0px;
+    border-radius: 10px;
+    box-shadow: rgba(0, 0, 0, 0.15) 0px 2px 4px;
+    background-color: rgb(51, 102, 171);
+    text-align: center;
+    line-height: 40px;
+}
+.title .button:hover {
+    cursor: pointer;
+    filter: brightness(1.1);
 }
 .chart-box {
     width: 100%;
@@ -155,9 +168,38 @@
     height: 262px;
 }
 
-/*
+.list > div:nth-child(1) {
+    width: 100%;
+    height: 40px;
     border-bottom: 2px solid rgba(102, 102, 102, 0.3);
     color: rgb(51, 102, 171);
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    font-weight: bold;
+}
+.list > div:nth-child(2) {
+    width: 100%;
+    height: calc(100% - 40px);
+    overflow: auto;
+}
+
+.list > div:nth-child(2) > div {
+    display: flex;
+    align-items: center;
+}
+.list > div:nth-child(1) > div,
+.list > div:nth-child(2) > div > div {
+    width: 50%;
+    padding: 10px;
+    text-align: center;
+}
+.list > div:nth-child(2) > div > div {
+    color: #a1a1a1;
+    font-weight: bold;
+}
+/*
+
 */
 
 @media (max-width: 920px) {

binární
src/main/resources/static/images/icon/logout.png


+ 9 - 0
src/main/resources/static/js/common/common.js

@@ -46,4 +46,13 @@ function moveNoticeView(boarNo, isAdmin) {
             moveNoticeView(boarNo);
         }
     }, null);
+}
+
+function dateFormatter(date) {
+    let year  = date.getFullYear().toString();
+    let month = date.getMonth() + 1;
+    let day   = date.getDate();
+    month = month < 10 ? "0" + month : month;
+    day   = day < 10 ? "0" + day : day;
+    return year + month + day;
 }

+ 112 - 0
src/main/resources/templates/admin/cctv.html

@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="en" xmlns:th=http://www.thymeleaf.org>
+<head>
+    <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width,initial-scale=1"/>
+    <meta name="theme-color" content="#000000"/>
+    <meta name="description" content="포항시 교통정보센터입니다"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+    <title>포항시 교통정보센터</title>
+    <th:block th:include="/include/head.html"></th:block>
+    <link rel="stylesheet" th:href="@{/css/cctv.css}">
+    <link rel="stylesheet" th:href="@{/css/video-7.10.2.css}">
+    <script th:src="@{/js/videojs/video-7.10.2.js}"></script>
+</head>
+<body id="body">
+<th:block th:include="/include/admin-header.html"></th:block>
+<div class="cctvWrap">
+    <div class="container">
+        <h2 class="admin-header">CCTV 관리</h2>
+        <div th:class="tab">
+            <div class="cctv active" onclick="tabActive('smart', 'cctv')">교통 CCTV</div>
+            <div class="smart" onclick="tabActive('cctv', 'smart')">스마트교차로 CCTV</div>
+        </div>
+        <div class="admin-content">
+            <div class="left">
+                <div class="title">교통 CCTV 목록</div>
+                <div class="list"></div>
+            </div>
+            <div class="right">
+                <div class="title">영상화면은 30초 동안 재생됩니다. <label class="toggle-button"><input id="switch" role="switch" type="checkbox"></label></div>
+            </div>
+        </div>
+    </div>
+</div>
+<th:block th:include="/include/footer.html"></th:block>
+</body>
+</html>
+
+<script th:inline="javascript">
+    const cctv = [[${cctv}]];
+    const intersection = [[${intersection}]];
+    let cctvStr = "";
+    let intersectionStr = "";
+    const strMap = new Map();
+    const detailMap = new Map();
+    const cctvMap = new Map();
+    const ixrMap  = new Map();
+
+    const $list  = $('.admin-content .left .list');
+    const $title = $('.admin-content .left .title');
+    const $switch = $('#switch');
+
+    if (intersection && intersection.length > 0) {
+        intersection.sort(function(a, b){
+            return a.ixr_nm > b.ixr_nm ? 1 : a.ixr_nm < b.ixr_nm ? -1 : 0;
+        });
+
+        intersection.forEach((obj)=>{
+            intersectionStr += `<ul id="${obj.ixr_id}" onclick="ixrClick('${obj.ixr_id}')">
+                                    <div>${obj.ixr_nm}</div>
+                                    <ul class="cmra-list">`
+            ixrMap.set(obj.ixr_id, obj);
+            obj.detail.forEach((camera)=>{
+                intersectionStr += `<li id="${camera.cmra_id}" onclick="detailClick('${camera.cmra_id}')">${camera.drct_lctn}</li>`;
+                detailMap.set(camera.cmra_id, camera);
+            })
+            intersectionStr += `</ul></ul>`;
+
+
+        });
+        strMap.set('smart', intersectionStr);
+
+    }
+
+    if (cctv && cctv.length > 0) {
+        cctv.forEach((obj)=>{
+            cctvStr +=`<ul onclick="cctvClick('${obj.cctv_mngm_nmbr}')"><div>${obj.istl_lctn_nm}</div></ul>`;
+            cctvMap.set(obj.cctv_mngm_nmbr, obj);
+        });
+        $list.html(cctvStr);
+        strMap.set('cctv', cctvStr);
+    }
+
+    function tabActive(hide, show) {
+        const $show = $('.' + show);
+        $('.' + hide).removeClass('active');
+        $show.addClass("active");
+
+        $list.html(strMap.get(show));
+        $title.text($show.text() + ' 목록');
+    }
+    function cctvClick(cctvNo) {
+        const obj = cctvMap.get(Number(cctvNo));
+        if (obj) {
+            console.log(obj);
+        }
+    }
+
+    function ixrClick(ixrNo) {
+        $('ul.active').removeClass("active");
+        $('ul#'+ixrNo).addClass('active');
+    }
+
+    function detailClick(cmraId) {
+        const obj = detailMap.get(cmraId);
+        if (obj) {
+            const useYn = (obj.cmra_use_yn === 1);
+            $switch.prop("checked", useYn);
+            console.log(useYn);
+        }
+    }
+</script>

+ 58 - 7
src/main/resources/templates/admin/conn-statistics.html

@@ -9,6 +9,7 @@
     <title>포항시 교통정보센터</title>
     <th:block th:include="/include/head.html"></th:block>
     <th:block th:include="/include/daterangepicker.html"></th:block>
+    <th:block th:include="/include/highchart.html"></th:block>
     <link rel="stylesheet" th:href="@{/css/conn-statistics.css}">
 </head>
 <body id="body">
@@ -19,11 +20,17 @@
         <div class="admin-content">
             <div class="title">
                 <input id="date" type="text">
+                <div th:class="button" onclick="searchStat()">조회</div>
             </div>
             <div class="chart-box">
                 <div id="chart"></div>
             </div>
             <div class="list">
+                <div>
+                    <div>기준일자</div>
+                    <div>방문자수</div>
+                </div>
+                <div class="list-content"></div>
             </div>
         </div>
     </div>
@@ -34,10 +41,14 @@
 
 <script>
     const $date = $('#date');
+    const $list = $('.list-content');
+    let startDate = new Date(new Date().setDate(new Date().getDate() - 9));
+    let endDate = new Date();
     const date = $date.daterangepicker({
         timePicker: false,
-        startDate: new Date(new Date().setDate(new Date().getDate() - 9)),
-        endDate: new Date(),
+        startDate: startDate,
+        endDate: endDate,
+        maxDate : new Date(),
         locale: {
             format: 'YYYY-MM-DD',
             separator: " ~ ",
@@ -53,21 +64,46 @@
     });
 
     function getConnStatistics(fromDt, toDt) {
-        getDataAsync("/api/common/conn-statistics", "POST", null, null, (jsonData)=>{
+        let period = Number(getDateDiff(toDt, fromDt));
+        let type = "";
+        let typeStr = "일";
+        let fromDate = dateFormatter(fromDt);
+        let toDate   = dateFormatter(toDt);
+        if (!isNaN(period)) {
+            if (period > 30) {
+                type = 'week';
+                typeStr = "주간";
+            }
+            if (period > 61) {
+                type = 'month';
+                typeStr = "월";
+                fromDate = fromDate.substring(0,6);
+                toDate = toDate.substring(0,6);
+            }
+        }
+
+        const param = {
+            fromDate : fromDate,
+            toDate   : toDate,
+            type     : type,
+        }
+        getDataAsync("/api/common/conn-statistics", "POST", param, typeStr, (jsonData)=>{
+            $list.empty();
             if (jsonData && jsonData.length > 0) {
-                drawChart(jsonData);
+                drawChart(jsonData, typeStr);
             }
         }, null);
     }
 
-    getConnStatistics();
+    getConnStatistics(startDate, endDate);
 
-    function drawChart(jsonData) {
+    function drawChart(jsonData, word) {
         let categories = [];
         let data = [];
         jsonData.forEach((obj)=>{
             categories.push(obj.conn_dt);
             data.push(obj.conn_cnt);
+            $list.append($(`<div><div>${obj.conn_dt}</div><div>${obj.conn_cnt}</div></div>`))
         })
         Highcharts.chart('chart', {
             chart: {
@@ -107,7 +143,7 @@
             },
             series: [
                 {
-                    name: '별접속자수',
+                    name: word + '별접속자수',
                     data: data
                 },
             ],
@@ -119,4 +155,19 @@
             },
         });
     }
+
+    function getDateDiff(date1, date2) {
+        const diffDate = date1.getTime() - date2.getTime();
+
+        return Math.abs(diffDate / (1000 * 60 * 60 * 24)); // 밀리세컨 * 초 * 분 * 시 = 일
+    }
+
+    function searchStat() {
+        const dateVal = $('#date').val();
+        if (isNull(dateVal)) {
+            return alert("조회 기간을 입력해주세요.");
+        }
+        const dateArr = dateVal.split(" ~ ");
+        getConnStatistics(new Date(dateArr[0]), new Date(dateArr[1]));
+    }
 </script>

+ 9 - 5
src/main/resources/templates/admin/main.html

@@ -9,11 +9,9 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
     <title>포항시 교통정보센터</title>
     <th:block th:include="/include/head.html"></th:block>
+    <th:block th:include="/include/highchart.html"></th:block>
     <link rel="stylesheet" th:href="@{/css/admin-main.css}">
-    <script th:src="@{/js/highchart/highcharts.js}"></script>
-    <script th:src="@{/js/highchart/modules/accessibility.js}"></script>
-    <script th:src="@{/js/highchart/modules/export-data.js}"></script>
-    <script th:src="@{/js/highchart/modules/exporting.js}"></script>
+
 </head>
 <body id="body">
 <th:block th:include="/include/admin-header.html"></th:block>
@@ -58,7 +56,13 @@
 <script th:inline="javascript">
 
     function getConnStatistics() {
-        getDataAsync("/api/common/conn-statistics", "POST", null, null, (jsonData)=>{
+        const fromDate = dateFormatter(new Date(new Date().setDate(new Date().getDate() - 7)));
+        const toDate = dateFormatter(new Date());
+        const param = {
+            fromDate : fromDate,
+            toDate : toDate,
+        }
+        getDataAsync("/api/common/conn-statistics", "POST", param, null, (jsonData)=>{
             if (jsonData && jsonData.length > 0) {
                 drawChart(jsonData);
             }

+ 2 - 2
src/main/resources/templates/include/admin-header.html

@@ -19,8 +19,8 @@
             <div class="top-menu-cont" th:classappend="${selected == 'cctv'} ? ' on' : ''">
                 <a th:href="@{/phits/cctv-list}">CCTV 관리</a>
             </div>
-            <span id="menu" onclick="logout()">
-                <img src="/images/icon/menu.png" alt="menu">
+            <span id="logout" onclick="logout()" title="로그아웃">
+                <img src="/images/icon/logout.png" alt="로그아웃">
             </span>
         </div>
     </div>

+ 4 - 0
src/main/resources/templates/include/highchart.html

@@ -0,0 +1,4 @@
+<script th:src="@{/js/highchart/highcharts.js}"></script>
+<script th:src="@{/js/highchart/modules/accessibility.js}"></script>
+<script th:src="@{/js/highchart/modules/export-data.js}"></script>
+<script th:src="@{/js/highchart/modules/exporting.js}"></script>