diff --git a/src/main/kotlin/codel/admin/presentation/AdminController.kt b/src/main/kotlin/codel/admin/presentation/AdminController.kt index 2dd171f..f7ae5fa 100644 --- a/src/main/kotlin/codel/admin/presentation/AdminController.kt +++ b/src/main/kotlin/codel/admin/presentation/AdminController.kt @@ -320,7 +320,9 @@ class AdminController( "PENDING" to adminService.countMembersByStatus("PENDING"), "DONE" to adminService.countMembersByStatus("DONE"), "REJECT" to adminService.countMembersByStatus("REJECT"), - "PHONE_VERIFIED" to adminService.countMembersByStatus("PHONE_VERIFIED") + "PHONE_VERIFIED" to adminService.countMembersByStatus("PHONE_VERIFIED"), + "WITHDRAWN" to adminService.countMembersByStatus("WITHDRAWN"), + "PERSONALITY_COMPLETED" to adminService.countMembersByStatus("PERSONALITY_COMPLETED") ) model.addAttribute("members", members) @@ -365,11 +367,9 @@ class AdminController( val questions = adminService.findQuestionsWithFilter(keyword, category, isActive, pageable) model.addAttribute("questions", questions) model.addAttribute("categories", QuestionCategory.values()) - model.addAttribute("param", mapOf( - "keyword" to (keyword ?: ""), - "category" to (category ?: ""), - "isActive" to (isActive?.toString() ?: "") - )) + model.addAttribute("selectedKeyword", keyword ?: "") + model.addAttribute("selectedCategory", category ?: "") + model.addAttribute("selectedIsActive", isActive?.toString() ?: "") return "questionList" } @@ -400,11 +400,21 @@ class AdminController( @GetMapping("/v1/admin/questions/{questionId}/edit") fun editQuestionForm( @PathVariable questionId: Long, + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false) category: String?, + @RequestParam(required = false) isActive: String?, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "20") size: Int, model: Model ): String { val question = adminService.findQuestionById(questionId) model.addAttribute("question", question) model.addAttribute("categories", QuestionCategory.values()) + model.addAttribute("filterKeyword", keyword) + model.addAttribute("filterCategory", category) + model.addAttribute("filterIsActive", isActive) + model.addAttribute("filterPage", page) + model.addAttribute("filterSize", size) return "questionEditForm" } @@ -415,6 +425,11 @@ class AdminController( @RequestParam category: String, @RequestParam(required = false) description: String?, @RequestParam(defaultValue = "false") isActive: Boolean, + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false) filterCategory: String?, + @RequestParam(required = false) filterIsActive: String?, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "20") size: Int, redirectAttributes: RedirectAttributes ): String { try { @@ -424,12 +439,27 @@ class AdminController( } catch (e: Exception) { redirectAttributes.addFlashAttribute("error", "질문 수정에 실패했습니다: ${e.message}") } - return "redirect:/v1/admin/questions" + + // 필터 조건 유지하여 리다이렉트 + val params = mutableListOf() + keyword?.let { if (it.isNotBlank()) params.add("keyword=$it") } + filterCategory?.let { if (it.isNotBlank()) params.add("category=$it") } + filterIsActive?.let { if (it.isNotBlank()) params.add("isActive=$it") } + params.add("page=$page") + params.add("size=$size") + + val queryString = if (params.isNotEmpty()) "?${params.joinToString("&")}" else "" + return "redirect:/v1/admin/questions$queryString" } @PostMapping("/v1/admin/questions/{questionId}/delete") fun deleteQuestion( @PathVariable questionId: Long, + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false) category: String?, + @RequestParam(required = false) isActive: String?, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "20") size: Int, redirectAttributes: RedirectAttributes ): String { try { @@ -438,12 +468,27 @@ class AdminController( } catch (e: Exception) { redirectAttributes.addFlashAttribute("error", "질문 삭제에 실패했습니다: ${e.message}") } - return "redirect:/v1/admin/questions" + + // 필터 조건 유지하여 리다이렉트 + val params = mutableListOf() + keyword?.let { if (it.isNotBlank()) params.add("keyword=$it") } + category?.let { if (it.isNotBlank()) params.add("category=$it") } + isActive?.let { if (it.isNotBlank()) params.add("isActive=$it") } + params.add("page=$page") + params.add("size=$size") + + val queryString = if (params.isNotEmpty()) "?${params.joinToString("&")}" else "" + return "redirect:/v1/admin/questions$queryString" } @PostMapping("/v1/admin/questions/{questionId}/toggle") fun toggleQuestionStatus( @PathVariable questionId: Long, + @RequestParam(required = false) keyword: String?, + @RequestParam(required = false) category: String?, + @RequestParam(required = false) isActive: String?, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "20") size: Int, redirectAttributes: RedirectAttributes ): String { try { @@ -453,7 +498,17 @@ class AdminController( } catch (e: Exception) { redirectAttributes.addFlashAttribute("error", "질문 상태 변경에 실패했습니다: ${e.message}") } - return "redirect:/v1/admin/questions" + + // 필터 조건 유지하여 리다이렉트 + val params = mutableListOf() + keyword?.let { if (it.isNotBlank()) params.add("keyword=$it") } + category?.let { if (it.isNotBlank()) params.add("category=$it") } + isActive?.let { if (it.isNotBlank()) params.add("isActive=$it") } + params.add("page=$page") + params.add("size=$size") + + val queryString = if (params.isNotEmpty()) "?${params.joinToString("&")}" else "" + return "redirect:/v1/admin/questions$queryString" } // ========== 신고 관리 ========== diff --git a/src/main/kotlin/codel/member/business/MemberService.kt b/src/main/kotlin/codel/member/business/MemberService.kt index 827124c..4a649da 100644 --- a/src/main/kotlin/codel/member/business/MemberService.kt +++ b/src/main/kotlin/codel/member/business/MemberService.kt @@ -701,11 +701,32 @@ class MemberService( null } + // 날짜 파싱 - yyyy-MM-dd 형식의 문자열을 LocalDateTime으로 변환 + val startDateTime = if (!startDate.isNullOrBlank()) { + try { + LocalDate.parse(startDate).atStartOfDay() // 00:00:00 + } catch (e: Exception) { + null + } + } else { + null + } + + val endDateTime = if (!endDate.isNullOrBlank()) { + try { + LocalDate.parse(endDate).plusDays(1).atStartOfDay() // 다음 날 00:00:00 (해당일 23:59:59까지 포함) + } catch (e: Exception) { + null + } + } else { + null + } + // 정렬 처리를 위한 새로운 Pageable 생성 val sortedPageable = createSortedPageable(pageable, sort, direction) - // 새로운 메서드 사용 - return memberJpaRepository.findMembersWithFilterAdvanced(keyword, statusEnum, sortedPageable) + // 새로운 메서드 사용 (날짜 파라미터 추가) + return memberJpaRepository.findMembersWithFilterAdvanced(keyword, statusEnum, startDateTime, endDateTime, sortedPageable) } /** diff --git a/src/main/kotlin/codel/member/infrastructure/MemberJpaRepository.kt b/src/main/kotlin/codel/member/infrastructure/MemberJpaRepository.kt index f1cc9de..c3080d6 100644 --- a/src/main/kotlin/codel/member/infrastructure/MemberJpaRepository.kt +++ b/src/main/kotlin/codel/member/infrastructure/MemberJpaRepository.kt @@ -85,14 +85,17 @@ interface MemberJpaRepository : JpaRepository { WHERE (:status IS NULL OR m.memberStatus = :status) AND ( :keyword IS NULL OR :keyword = '' - OR LOWER(m.email) LIKE LOWER(CONCAT('%', :keyword, '%')) OR LOWER(p.codeName) LIKE LOWER(CONCAT('%', :keyword, '%')) ) + AND (:startDate IS NULL OR m.createdAt >= :startDate) + AND (:endDate IS NULL OR m.createdAt < :endDate) """ ) fun findMembersWithFilterAdvanced( @Param("keyword") keyword: String?, @Param("status") status: MemberStatus?, + @Param("startDate") startDate: LocalDateTime?, + @Param("endDate") endDate: LocalDateTime?, pageable: Pageable ): Page diff --git a/src/main/resources/templates/memberList.html b/src/main/resources/templates/memberList.html index f663b90..ae3aa8d 100644 --- a/src/main/resources/templates/memberList.html +++ b/src/main/resources/templates/memberList.html @@ -186,8 +186,8 @@

0

거부됨
-

0

- 핸드폰 인증 완료 +

0

+ 탈퇴

0%

@@ -213,6 +213,8 @@

승인 완료 + +

@@ -295,6 +297,22 @@

0 + + @@ -401,6 +419,12 @@

핸드폰 인증 완료 + + 오픈프로필 작성 완료 + + + 탈퇴 + diff --git a/src/main/resources/templates/questionEditForm.html b/src/main/resources/templates/questionEditForm.html index e6d76b3..11afb93 100644 --- a/src/main/resources/templates/questionEditForm.html +++ b/src/main/resources/templates/questionEditForm.html @@ -41,7 +41,8 @@

질문 수정

- + 목록으로
@@ -49,9 +50,16 @@

질문 수정

+ + + + + + +
-
최대 500자까지 입력 가능합니다.
@@ -91,7 +99,8 @@

질문 수정

- + 취소
diff --git a/src/main/resources/templates/questionList.html b/src/main/resources/templates/questionList.html index 311ca34..74f617f 100644 --- a/src/main/resources/templates/questionList.html +++ b/src/main/resources/templates/questionList.html @@ -50,24 +50,24 @@

질문 관리

+ th:value="${selectedKeyword}" placeholder="질문 내용 또는 설명 검색">
@@ -119,12 +119,17 @@

질문 관리

- + + + + +

+ + + + +