SpringBoot 프로젝트

Spring Boot - 관리자 페이지 (5)

orin602 2024. 12. 7. 17:18

지난 글에 이어서 관리자 페이지에서의 회원 질문 관리에 대한 기능 구현과 설명을 이어가겠습니다.

부족하더라도 이해해주세요. :)

 

adminMain.html

<!-- 질문관리 섹션 -->
<div class="admin-section">
    <h3 class="clickable" onclick="toggleContent(this)">질문 관리</h3>
    <div class="content-list" style="display: none;">
        <a th:href="@{/admin-fix-qna}">고정 질문</a><br>
        <a th:href="@{/admin-customer-qna}">회원 질문</a><br>
    </div>
</div>
  • 회원 질문 링크 클릭 시 이전 글에서 미리 만들어 둔 Controller 처리로 회원 질문 페이지로 이동.
// 회원질문 페이지
@GetMapping("/admin-customer-qna")
public String customerQuestions(HttpSession session, Model model) {
    Member admin = (Member)session.getAttribute("admin");
    if(admin == null) {
        model.addAttribute("message", "로그인 페이지로 이동");
        model.addAttribute("text", "회원관리를 위해 로그인해주세요.");
        model.addAttribute("messageType", "info");

        return "admin/admin_login";
    }
    // 회원 질문 목록을 가져오는 로직 구현
    List<Qna> customerQna = qnaService.getCustomerQna();
    model.addAttribute("customerQna", customerQna);

    return "admin/section/customer_questions"; // 뷰 이름
}
  • customer_questions.html로 렌더링.

회원 질문 페이지 (customer_question.html)

<div class="all-container">
    <h1>회원 질문 관리</h1>
    <div class="customer-qnas" th:if="${customerQna.isEmpty()}">
    	<h3>아직 회원 질문이 없습니다..</h3>
    </div>
    <div class="customer-qnas" th:unless="${customerQna.isEmpty()}">
        <table>
            <thead>
                <tr>
                	<th>질문자 ID</th>
                	<th>제목</th>
                	<th>내용</th>
                	<th>질문 날짜</th>
                	<th>답변 여부</th>
                	<th></th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="qna : ${customerQna}">
                	<td th:text="${qna.member.id}">질문자 ID</td>
                	<td th:text="${qna.title}">제목</td>
                	<td th:text="${qna.content}">내용</td>
                	<td th:text="${#dates.format(qna.qna_date, 'yyyy-MM-dd HH:mm')}">질문 날짜</td>
                	<td>
                        <span th:if="${qna.answer_status == 0}">대기</span>
                        <span th:if="${qna.answer_status == 1}">완료</span>
                    </td>
                    <td>
                        <button th:if="${qna.answer_status == 0}" class="answer-btn" th:data-seq="${qna.qna_seq}"
                        	onclick="customer_qna_answer(this)">
                        	답변 작성
                        </button>
                        <button th:if="${qna.answer_status == 1}" class="disabled-btn" disabled>
                        	답변 완료
                        </button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
  • 조건부 렌더링 : th:if="${qna.answer_status == 0}" > 해당 질문의 답변이 없는 경우에만 버튼이 표시 , th:if="${qna.answer_status == 1}" > 마찬가지로 답변이 있는 경우에만 버튼 표시.

아직 작성 되지 않은 회원 질문.
테스트용으로 작성

  • 답변 작성 버튼 클릭 시 JavaScript 함수 (customer_qna_answer)
// 회원질문 답변
function customer_qna_answer(button) {
	const qna_seq = button.getAttribute('data-seq');
	
	swal.fire({
		title: '답변 작성',
		input: 'textarea',
		inputLabel: '답변을 입력하세요.',
		inputPlaceholder: '답변은 여기에 입력해주세요!',
		showCancelButton: true,
		confirmButtonText: '제출',
        cancelButtonText: '취소',
        preConfirm: (answer) => {
			if(!answer) {
				swal.showValidationMessage('답변을 작성하지 않았어요..~!');
			}
			return answer;
		}
	}).then((result) => {
		if(result.isConfirmed) {
			$.ajax({
				url: '/customer-qna-answer',
				type: 'POST',
				data: {
					qna_seq: qna_seq,
					answer: result.value
				},
				success: function(response) {
					swal.fire({
						title: '답변 성공!',
						text: '답변을 성공적으로 제출하였습니다.',
						icon: 'success',
						confirmButtonText: '확인' 
					});
					location.reload();
				},
				error: function(xhr) {
					swal.fire('오류', '답변 제출 실패.', 'error');
				}
			});
		}
	});
}
  • input: 'textarea', : 답변을 입력하는 텍스트 영역을 SweetAlert2 창으로 제공.
  • if(!answer) { swal.showValidationMessage('답변을 작성하지 않았어요..~!'); } : 입력값이 없을 경우 메시지 표시.
  • $.ajax({ url: '/customer-qna-answer',type: 'POST',data: { qna_seq: qna_seq, answer: result.value }, : Ajax 요청 > url을 ' '로 POST 요청, data는 답변할 질문의 고유 번호(qna_seq), 작성된 답변 (answer)를 전달.
  • 성공 시 swal.fire({...}); 성공 메시지 표시, location.reload(); 페이지 새로고침.
  • 실패 시 swal.fire('오류', '답변 제출 실패.', 'error'); 오류 메시지.

Controller 처리

// 회원질문 답변처리
@PostMapping("/customer-qna-answer")
@ResponseBody
public ResponseEntity<String> submitCustomerQnaAnswer(HttpSession session, Model model,
        @RequestParam("qna_seq") int qna_seq, @RequestParam("answer") String answer) {
    Member admin = (Member)session.getAttribute("admin");
    if(admin == null) {
        model.addAttribute("message", "로그인 페이지로 이동");
        model.addAttribute("text", "회원관리를 위해 로그인해주세요.");
        model.addAttribute("messageType", "info");

        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 필요");
    }

    Qna qna = qnaService.findQnaBySeq(qna_seq);
    if(qna != null) {
        qna.setAnswer(answer);
        qna.setAnswer_date(new Date());
        qna.setAnswer_status(1);

        qnaService.updateQna(qna);
        return ResponseEntity.ok("답변이 성공적으로 제출되었습니다.");
    } else {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body("질문을 찾을 수 없습니다.");
    }
}
  • @PostMapping("/customer-qna-answer") : url 경로에 대한 POST 요청 처리, Ajax 요청에 데이터를 받는다.
  • @ResponseBody : 응답 형태를 JSON 형식로 반환.
  • @RequestParam("qna_seq") int qna_seq, @RequestParam("answer") String answer : HTTP 요청에 포함된 파라미터 값을 받음.
  • Qna qna = qnaService.findQnaBySeq(qna_seq); : 전달받은 질문 고유 번호(qna_seq)로 질문을 찾아 Qna 객체로 저장.
  • if(qna != null) { qna.setAnswer(answer); > 파라미터로 전달받은 답변을 해당 질문의 답변으로 설정. qna.setAnswer_date(new Date()); > 현재 날짜와 시간을 답변 작성 날짜로 설정. qna.setAnswer_status(1); > 답변 상태를 1(답변 환료)로 변경. qnaService.updateQna(qna); > DB에 저장된 질문 객체를 업데이트.
  • 반환값 : return ResponseEntity.ok("답변이 성공적으로 제출되었습니다."); > 200상태 코드와 메시지. return ResponseEntity.status(HttpStatus.NOT_FOUND).body("질문을 찾을 수 없습니다."); > 404 상태 코드와 메시지.

++ 테스트

답변을 작성하지 않고 제출 버튼 클릭 시
답변 작성 후 제출 버튼 클릭 시
DB 결과
마이페이지 에서의 모습