From ce36a2914da1f79a1f4e3f618d83001186efa17d Mon Sep 17 00:00:00 2001 From: HeHelee Date: Mon, 15 Sep 2025 12:01:50 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EB=A7=81=20=EC=88=98=EC=A0=95=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../snapshot/qure/config/SecurityConfig.java | 17 +--- .../controller/InspectorsController.java | 16 +++- .../WEB-INF/assets/css/inspectorsList.css | 52 +++++++++-- .../webapp/WEB-INF/assets/images/plus.svg | 4 +- .../webapp/WEB-INF/assets/images/trash.svg | 6 +- .../WEB-INF/assets/js/inspectorsList.js | 90 +++++++++---------- .../views/inspectors/inspectorsList.jsp | 45 +++++----- 7 files changed, 133 insertions(+), 97 deletions(-) diff --git a/src/main/java/app/snapshot/qure/config/SecurityConfig.java b/src/main/java/app/snapshot/qure/config/SecurityConfig.java index d7dacec..4c68294 100644 --- a/src/main/java/app/snapshot/qure/config/SecurityConfig.java +++ b/src/main/java/app/snapshot/qure/config/SecurityConfig.java @@ -44,25 +44,13 @@ public SecurityConfig(KakaoOAuth2UserService kakaoOAuth2UserService, this.kakaoOAuth2UserService = kakaoOAuth2UserService; this.managerService = managerService; } - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth - .requestMatchers( - AntPathRequestMatcher.antMatcher("/"), - AntPathRequestMatcher.antMatcher("/assets/**"), - AntPathRequestMatcher.antMatcher("/css/**"), - AntPathRequestMatcher.antMatcher("/js/**"), - AntPathRequestMatcher.antMatcher("/images/**"), - AntPathRequestMatcher.antMatcher("/webjars/**"), - AntPathRequestMatcher.antMatcher("/favicon.*"), - AntPathRequestMatcher.antMatcher("/oauth2/**"), - AntPathRequestMatcher.antMatcher("/login/oauth2/**"), - AntPathRequestMatcher.antMatcher("/error") - ).permitAll() - .anyRequest().authenticated() + .anyRequest().permitAll() ) + .csrf(csrf -> csrf.disable()) // 세션 관리 설정 .sessionManagement(session -> session @@ -95,7 +83,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } - @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); diff --git a/src/main/java/app/snapshot/qure/inspectors/controller/InspectorsController.java b/src/main/java/app/snapshot/qure/inspectors/controller/InspectorsController.java index a8df35d..22bbc34 100644 --- a/src/main/java/app/snapshot/qure/inspectors/controller/InspectorsController.java +++ b/src/main/java/app/snapshot/qure/inspectors/controller/InspectorsController.java @@ -19,17 +19,27 @@ public InspectorsController(InspectorsService inspectorsService) { @GetMapping("/inspectors/inspectorsList") public String showInspectorList(@RequestParam(defaultValue = "1") int page, - Model model - ) { - int size = 15; + Model model) { + int size = 15; // 한 페이지 데이터 수 List inspectors = inspectorsService.getPagedInspectors(page, size); int totalCount = inspectorsService.getTotalCount(); int totalPages = (int) Math.ceil((double) totalCount / size); + int pageBlock = 3; + int currentBlock = (int) Math.ceil((double) page / pageBlock); + + int startPage = (currentBlock - 1) * pageBlock + 1; + int endPage = startPage + pageBlock - 1; + if (endPage > totalPages) { + endPage = totalPages; + } model.addAttribute("inspectors", inspectors); model.addAttribute("currentPage", page); model.addAttribute("totalPages", totalPages); model.addAttribute("totalCount", totalCount); + model.addAttribute("startPage", startPage); + model.addAttribute("endPage", endPage); + return "inspectors/inspectorsList"; } } \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/assets/css/inspectorsList.css b/src/main/webapp/WEB-INF/assets/css/inspectorsList.css index 1b05b21..c682d22 100644 --- a/src/main/webapp/WEB-INF/assets/css/inspectorsList.css +++ b/src/main/webapp/WEB-INF/assets/css/inspectorsList.css @@ -54,7 +54,7 @@ body{ display:flex;justify-content:space-between;align-items:center; background:#ffffff; border-radius:30px; - padding:12px 16px; + padding:12px 16x; } .subTitle{ color:#5a4a21; @@ -86,8 +86,15 @@ button:hover{background:#fafafa} /* “전체 n명” 라벨 */ .totalTitle{color:#474D56;font-size:16px;font-weight:700;margin-right:6px} -.highlight{color:#5a4a21;background:#fff8d1;border:1px solid #eacb6a; - padding:2px 8px;border-radius:999px;font-weight:800} +.highlight{ + color:#5a4a21; + background:#fff8d1; + border:1px solid #eacb6a; + padding:2px 8px; + border-radius:999px; + font-weight:800; + +} /* ===== 표: 행마다 보더 박스 ===== */ .inspectorTable{ @@ -97,6 +104,7 @@ button:hover{background:#fafafa} background: transparent; table-layout: fixed; font-size: 15px; + margin-top: 16px; } /* 헤더 */ @@ -152,9 +160,11 @@ input[type="checkbox"]:checked::after{ a{color:inherit;text-decoration:none} /* ===== 페이지네이션: facilities 원형 버튼 스타일 ===== */ -.pagination{ - display:flex;justify-content:center;align-items:center;gap:12px; - margin-top:8px; +.pagination { + display: flex; + justify-content: center; + gap: 8px; + margin-top: 16px; } .circle-btn{ @@ -169,6 +179,36 @@ a{color:inherit;text-decoration:none} } .circle-btn.disabled{pointer-events:none;opacity:.45} +.page-btn { + display: flex; + justify-content: center; + align-items: center; + width: 36px; + height: 36px; + border: 1px solid #f2c65b; + border-radius: 6px; + font-size: 15px; + font-weight: 600; + color: #3c3c3c; + text-decoration: none; + background: #fff; + cursor: pointer; +} + +.page-btn:hover { + background: #fff9e6; +} + +.page-btn.active { + background: #FFE59A; + font-weight: 800; +} + +.page-btn.disabled { + pointer-events: none; + opacity: 0.4; +} + /* ===== 모달: facilities 카드 톤 매칭 ===== */ .modal{ display:none;position:fixed;z-index:1000;left:0;top:0;width:100%;height:100%; diff --git a/src/main/webapp/WEB-INF/assets/images/plus.svg b/src/main/webapp/WEB-INF/assets/images/plus.svg index edffae5..5e7774b 100644 --- a/src/main/webapp/WEB-INF/assets/images/plus.svg +++ b/src/main/webapp/WEB-INF/assets/images/plus.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/main/webapp/WEB-INF/assets/images/trash.svg b/src/main/webapp/WEB-INF/assets/images/trash.svg index 011a91e..a9e69f7 100644 --- a/src/main/webapp/WEB-INF/assets/images/trash.svg +++ b/src/main/webapp/WEB-INF/assets/images/trash.svg @@ -1,3 +1,5 @@ - - + + + + diff --git a/src/main/webapp/WEB-INF/assets/js/inspectorsList.js b/src/main/webapp/WEB-INF/assets/js/inspectorsList.js index 626bc0b..2db43aa 100644 --- a/src/main/webapp/WEB-INF/assets/js/inspectorsList.js +++ b/src/main/webapp/WEB-INF/assets/js/inspectorsList.js @@ -1,4 +1,3 @@ -// inspectorsList.js function onlyNumber(el) { el.value = el.value.replace(/[^0-9]/g, ''); @@ -9,12 +8,13 @@ function onlyNumber(el) { document.getElementById("phone").value = (p1 && p2 && p3) ? `${p1}-${p2}-${p3}` : ""; } + +document.addEventListener("DOMContentLoaded", function () { const modal = document.getElementById("inspectorModal"); const openModalBtn = document.querySelector(".register"); const cancelBtn = document.querySelector(".btn.cancel"); const form = modal ? modal.querySelector("form") : null; - // 모달 열기 if (openModalBtn) { openModalBtn.addEventListener("click", (e) => { e.preventDefault(); @@ -22,17 +22,15 @@ function onlyNumber(el) { }); } - // 취소 버튼 눌렀을 때 닫기 + reset if (cancelBtn) { cancelBtn.addEventListener("click", (e) => { e.preventDefault(); if (form) form.reset(); - document.getElementById("phone").value = ""; // hidden 필드도 초기화 + document.getElementById("phone").value = ""; modal.style.display = "none"; }); } - // 바깥 클릭 시 닫기 if (modal) { modal.addEventListener("click", (e) => { if (e.target === modal) { @@ -80,49 +78,49 @@ function onlyNumber(el) { }); } - if (form) { - form.addEventListener("submit", function () { - const p1 = document.getElementById("phone1").value; - const p2 = document.getElementById("phone2").value; - const p3 = document.getElementById("phone3").value; - - document.getElementById("phone").value = - (p1 && p2 && p3) ? `${p1}-${p2}-${p3}` : ""; - }); - } - - const deleteBtn = document.getElementById("deleteBtn"); - const deleteModal = document.getElementById("deleteModal"); - const deleteCancel = document.getElementById("deleteCancel"); - const selectedIdsInput = document.getElementById("selectedIdsInput"); - - if (deleteBtn) { - deleteBtn.addEventListener("click", () => { - const checkedBoxes = document.querySelectorAll("input[name='selectedIds']:checked"); - if (checkedBoxes.length === 0) { - alert("삭제할 작업자를 선택하세요."); - return; - } - - // 선택된 ID들을 hidden input에 담기 - const ids = Array.from(checkedBoxes).map(cb => cb.value); - selectedIdsInput.value = ids.join(","); - - deleteModal.style.display = "flex"; - }); - } + if (form) { + form.addEventListener("submit", function () { + const p1 = document.getElementById("phone1").value; + const p2 = document.getElementById("phone2").value; + const p3 = document.getElementById("phone3").value; - if (deleteCancel) { - deleteCancel.addEventListener("click", () => { - deleteModal.style.display = "none"; - }); + document.getElementById("phone").value = + (p1 && p2 && p3) ? `${p1}-${p2}-${p3}` : ""; + }); + } + + // 삭제 모달 + const deleteBtn = document.getElementById("deleteBtn"); + const deleteModal = document.getElementById("deleteModal"); + const deleteCancel = document.getElementById("deleteCancel"); + const selectedIdsInput = document.getElementById("selectedIdsInput"); + + if (deleteBtn) { + deleteBtn.addEventListener("click", () => { + const checkedBoxes = document.querySelectorAll("input[name='selectedIds']:checked"); + if (checkedBoxes.length === 0) { + alert("삭제할 작업자를 선택하세요."); + return; } - if (deleteModal) { - deleteModal.addEventListener("click", (e) => { - if (e.target === deleteModal) { - deleteModal.style.display = "none"; - } - }); + const ids = Array.from(checkedBoxes).map(cb => cb.value); + selectedIdsInput.value = ids.join(","); + + deleteModal.style.display = "flex"; + }); + } + + if (deleteCancel) { + deleteCancel.addEventListener("click", () => { + deleteModal.style.display = "none"; + }); + } + + if (deleteModal) { + deleteModal.addEventListener("click", (e) => { + if (e.target === deleteModal) { + deleteModal.style.display = "none"; } + }); + } }); diff --git a/src/main/webapp/WEB-INF/views/inspectors/inspectorsList.jsp b/src/main/webapp/WEB-INF/views/inspectors/inspectorsList.jsp index 6af9fc9..7002c33 100644 --- a/src/main/webapp/WEB-INF/views/inspectors/inspectorsList.jsp +++ b/src/main/webapp/WEB-INF/views/inspectors/inspectorsList.jsp @@ -22,10 +22,7 @@
-
+
작업자 현황
@@ -34,7 +31,7 @@ 작업자 등록
@@ -70,25 +67,30 @@ - ${currentPage} - - -
-
-
- - -
- -
- - -
@@ -41,19 +41,35 @@
담당 구역
- - ${f.domain} - + + + + ${f.domain} + + + + 없음 + +
+
담당 시설
- - ${f.facility} - + + + + ${f.facility} + + + + 없음 + +
+
From 994ee5353fed02ba060e9f56761da1d25be8c78d Mon Sep 17 00:00:00 2001 From: HeHelee Date: Mon, 15 Sep 2025 16:31:14 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../snapshot/qure/config/SecurityConfig.java | 30 +++++++++++----- .../WEB-INF/assets/js/inspectorsList.js | 36 ------------------- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/src/main/java/app/snapshot/qure/config/SecurityConfig.java b/src/main/java/app/snapshot/qure/config/SecurityConfig.java index 56da050..a42c769 100644 --- a/src/main/java/app/snapshot/qure/config/SecurityConfig.java +++ b/src/main/java/app/snapshot/qure/config/SecurityConfig.java @@ -48,14 +48,33 @@ public SecurityConfig(KakaoOAuth2UserService kakaoOAuth2UserService, public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth - .anyRequest().permitAll() + .requestMatchers( + AntPathRequestMatcher.antMatcher("/"), + AntPathRequestMatcher.antMatcher("/mobile/**"), + AntPathRequestMatcher.antMatcher("/popup/**"), + AntPathRequestMatcher.antMatcher("/assets/**"), + AntPathRequestMatcher.antMatcher("/css/**"), + AntPathRequestMatcher.antMatcher("/js/**"), + AntPathRequestMatcher.antMatcher("/images/**"), + AntPathRequestMatcher.antMatcher("/webjars/**"), + AntPathRequestMatcher.antMatcher("/favicon.*"), + AntPathRequestMatcher.antMatcher("/oauth2/**"), + AntPathRequestMatcher.antMatcher("/login/oauth2/**"), + AntPathRequestMatcher.antMatcher("/error") + ).permitAll() + .anyRequest().authenticated() ) - .csrf(csrf -> csrf.disable()) + + .csrf(csrf -> csrf.ignoringRequestMatchers( + new AntPathRequestMatcher("/mobile/**", "POST"), + new AntPathRequestMatcher("/popup/**", "POST") + )) // 세션 관리 설정 .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) + .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) ) + .oauth2Login(oauth -> oauth .loginPage("/") .successHandler((request, response, authentication) -> { @@ -64,17 +83,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { (Map) principal.getAttributes().get("kakao_account"); Map profile = (Map) kakaoAccount.get("profile"); - String email = (String) kakaoAccount.get("email"); String nickname = (String) profile.get("nickname"); - ManagerDTO manager = managerService.processLogin(email, nickname); - HttpSession session = request.getSession(); SessionUtil.setManagerSession(session, manager.getManagersId(), email, nickname); - System.out.println("로그인 성공: " + nickname + " (" + email + ")"); - response.sendRedirect(request.getContextPath() + "/dashboard"); }) .userInfoEndpoint(userInfo -> userInfo.userService(kakaoOAuth2UserService)) diff --git a/src/main/webapp/WEB-INF/assets/js/inspectorsList.js b/src/main/webapp/WEB-INF/assets/js/inspectorsList.js index 2db43aa..07240ec 100644 --- a/src/main/webapp/WEB-INF/assets/js/inspectorsList.js +++ b/src/main/webapp/WEB-INF/assets/js/inspectorsList.js @@ -41,42 +41,6 @@ document.addEventListener("DOMContentLoaded", function () { }); } - // 소속-설비 매핑 - const equipmentOptions = { - "소방": ["스프링쿨러", "소화기", "화재경보기"], - "순찰": ["CCTV", "비상벨", "출입통제"], - "청결": ["분리수거함", "화장실", "소독기"], - }; - - const domainSelect = document.getElementById("domain"); - const equipmentSelect = document.getElementById("equipment"); - - if (domainSelect && equipmentSelect) { - domainSelect.addEventListener("change", function () { - const selectDomain = this.value; - - equipmentSelect.innerHTML = ""; - - if (selectDomain && equipmentOptions[selectDomain]) { - const defaultOption = document.createElement("option"); - defaultOption.value = ""; - defaultOption.textContent = "설비를 선택하세요"; - equipmentSelect.appendChild(defaultOption); - - equipmentOptions[selectDomain].forEach((equip) => { - const option = document.createElement("option"); - option.value = equip; - option.textContent = equip; - equipmentSelect.appendChild(option); - }); - } else { - const option = document.createElement("option"); - option.value = ""; - option.textContent = "소속을 먼저 선택하세요."; - equipmentSelect.appendChild(option); - } - }); - } if (form) { form.addEventListener("submit", function () {